P4149 [IOI2011]Race

传送门

十分显然的点分治

枚举所有点作为两点的LCA

开一个桶$pd$判断之前子树内是否出现过此路程

对于每一个子树都把子树到根的所有路程dis都考虑匹配

如果 $pd[K-dis]=1$ 那么就说明存在匹配

然鹅题目还要求在合法匹配中选最少经过边数的匹配

那么再开一个数组 $dd$ ,$dd[i]$ 存当路程为 i 时经过的最少边数

dfs一个子树时开一个栈,存每个路程($st$)和经过的最少边数($d$)

那么dfs完一颗子树后就枚举栈中的所有元素,如果$pd[K-st[i]]=1$并且$dd[K-st[i]]+d[st[i]]<ans$,那么更新ans

再把$st$和$d$分别合并到$pd$和$dd$里,最后记得还原$d$

这种方法如果不点分治会被卡成$O(n^2)$,所以点分治一波就好了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) f=-1; ch=getchar(); }
    while(ch>=‘0‘&&ch<=‘9‘) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=1e6+7,M=1e6+7,INF=1e9+7;
int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt;
inline void add(int &a,int &b,int &c)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b; val[cntt]=c;
}
int n,K,tot,rt,ans=INF;
int sz[N],mx[N];
bool vis[N];
void find_rt(int x,int fa)
{
    sz[x]=1; mx[x]=0;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(vis[v]||v==fa) continue;
        find_rt(v,x); sz[x]+=sz[v];
        mx[x]=max(mx[x],sz[v]);
    }
    mx[x]=max(mx[x],tot-mx[x]);
    if(mx[x]<mx[rt]) rt=x;
}
int st[N],Top,d[M];
bool inst[M];//判断是否在栈中
void dfs(int x,int fa,int dis,int dep)//dis存当前的路程,dep是深度
{
    if(dis>K) return;//如果dis>K就不用dfs了,不会对答案产生贡献
    if(!inst[dis]) st[++Top]=dis,d[dis]=dep,inst[dis]=1;//如果dis不在栈就入栈
    else if(d[dis]>dep) d[dis]=dep;//否则考虑更新d
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(vis[v]||v==fa) continue;
        dfs(v,x,dis+val[i],dep+1);
    }
}
int dd[M],q[N],p;//q是回收池
bool pd[M];
void work(int x)
{
    p=0; pd[0]=1;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(vis[v]) continue;
        Top=0; dfs(v,x,val[i],1);
        for(int j=1;j<=Top;j++)//枚举所有路程尝试更新答案
            if(pd[K-st[j]]&&dd[K-st[j]]+d[st[j]]<ans)
                ans=dd[K-st[j]]+d[st[j]];
        for(int j=1;j<=Top;j++)//把st和d分别合进pd和dd
        {
            if(!pd[st[j]])
                pd[st[j]]=1,dd[st[j]]=d[st[j]],q[++p]=st[j];
            else if(dd[st[j]]>d[st[j]]) dd[st[j]]=d[st[j]];
            d[st[j]]=0,inst[st[j]]=0;//合完记得清空
        }
    }
    for(int i=1;i<=p;i++) pd[q[i]]=0,dd[q[i]]=0;//最后记得要把pd和dd全部还原
}
void solve(int x)//点分治
{
    vis[x]=1; work(x);
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(vis[v]) continue;
        tot=sz[v]; rt=0;
        find_rt(v,0); solve(rt);
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    n=read(),K=read();
    int a,b,c;
    for(int i=1;i<n;i++)
    {
        a=read(),b=read(),c=read();
        add(a,b,c); add(b,a,c);
    }
    tot=n; mx[0]=INF;
    find_rt(1,0); solve(rt);
    if(ans==INF) printf("-1");
    else printf("%d",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/LLTYYC/p/10308366.html

时间: 2024-10-13 23:49:49

P4149 [IOI2011]Race的相关文章

P4149 [IOI2011]Race 点分治

思路: 点分治 提交:5次 题解: 刚开始用排序+双指针写的,但是调了一晚上,总是有两个点过不了,第二天发现原因是排序时的\(cmp\)函数写错了:如果对于路径长度相同的,我们从小往大按边数排序,当双指针出现\(==k\)时,即我们应先左移右指针,否则答案可能会变劣(仔细想一想):若反着排序,应该先右移左指针. #include<bits/stdc++.h> #define R register int using namespace std; namespace Luitaryi { tem

BZOJ 2599: [IOI2011]Race( 点分治 )

数据范围是N:20w, K100w. 点分治, 我们只需考虑经过当前树根的方案. K最大只有100w, 直接开个数组CNT[x]表示与当前树根距离为x的最少边数, 然后就可以对根的子树依次dfs并更新CNT数组和答案. ------------------------------------------------------------------------------------------ #include<bits/stdc++.h> using namespace std; typ

【BZOJ2599】[IOI2011]Race 树的点分治

[BZOJ2599][IOI2011]Race Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) Output 一个整数 表示最小边数量 如果不存在这样的路径 输出-1 Sample Input 4 3 0 1 1 1 2 2 1 3 4 Sample Output 2 题解:本题大

【点分治】【哈希表】bzoj2599 [IOI2011]Race

给nlog2n随便过的跪了,不得已弄了个哈希表伪装成nlogn(当然随便卡,好孩子不要学)…… 不过为啥哈希表的大小开小点就RE啊……?必须得超过数据范围一大截才行……谜 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int f,c; inline void R(int &x){ c=0;f=1; for(;c<'0'||c>'9';c=getc

bzoj1758 [Wc2010]重建计划 &amp; bzoj2599 [IOI2011]Race

两题都是树分治. 1758这题可以二分答案avgvalue,因为avgvalue=Σv(e)/s,因此二分后只需要判断Σv(e)-s*avgvalue是否大于等于0,若大于等于0则调整二分下界,否则调整二分上界.假设一棵树树根为x,要求就是经过树根x的最大答案,不经过树根x的可以递归求解.假设B[i]为当前做到的一颗x的子树中的点到x的距离为i的最大权值,A[i]为之前已经做过的所有子数中的点到x的距离为i的最大权值(这里的权值是Σv(e)-i*avgvalue),那么对于当前子树的一个距离i,

bzoj2599: [IOI2011]Race(点分治)

写了四五道点分治的题目了,算是比较理解点分治是什么东西了吧= = 点分治主要用来解决点对之间的问题的,比如距离为不大于K的点有多少对. 这道题要求距离等于K的点对中连接两点的最小边数. 那么其实道理是一样的.先找重心,然后先从重心开始求距离dis和边数num,更新ans,再从重心的儿子开始求得dis和num,减去这部分答案 因为这部分的答案中,从重心开始的两条链有重叠部分,所以要剪掉 基本算是模板题,但是减去儿子的答案的那部分还有双指针那里调了好久,所以还不算特别熟练.. PS跑了27秒慢到飞起

bzoj 2599 [IOI2011]Race (点分治)

[题意] 问树中长为k的路径中包含边数最少的路径所包含的边数. [思路] 统计经过根的路径.假设当前枚举到根的第S个子树,若x属于S子树,则有: ans<-dep[x]+min{ dep[y] },y属于前S-1个子树,dis[x]<=K 所以只需要用一个数组t[len]记录前S-1棵子树中长度为len的最少边数即可.t只用开到K的最大值. 然后分治处理子树. [代码] 1 #include<set> 2 #include<cmath> 3 #include<qu

[BZOJ2599][IOI2011]Race

试题描述 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 输入 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) 输出 一个整数 表示最小边数量 如果不存在这样的路径 输出-1 输入示例 4 3 0 1 1 1 2 2 1 3 4 输出示例 2 数据规模及约定 见“试题描述” 题解 点分治裸题.我还调了半天TAT...好久没写什么都忘了... 每次找子树的中心往下递

【BZOJ2599】[IOI2011]Race【点分治】

[题目链接] 点分治. 考虑经过点x的路径,对于x,用类似TreeDP的方法,记录no[d],表示路径长度为d时经过边最少的点的编号. 对于已经走过的子树,更新no.对于当前子树,遍历到一个点v,用depth[no[k - dis[v]]] + depth[v]更新答案. 注意给no清零时,用dfs姿势清零,这样做是O(n)的.如果直接用for或者memset,这样做是O(k)的,会TLE. /* Telekinetic Forest Guard */ #include <cstdio> #i