【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
0 1 1
1 2 2
1 3 4
Sample Output
2
题解:本题大部分代码都与POJ1741那道模板题相同,只不过是calc函数里有些不一样
如果我们想计算一棵子树里的答案,仍然是先按照每个点到根的距离排序,然后用双指针法算出长度=m的路径,那么问题来了,我们怎样将答案中的非简单路径去掉呢?
由于我们最终要求的是经过边最少的长度=m的简单路径,那么我们可以用ans[i]保存经过i条边的路径数,然后在calc的时候直接修改ans就行了
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn=200010; int to[maxn<<1],next[maxn<<1],val[maxn<<1],dep[maxn],s[maxn],head[maxn],siz[maxn]; int vis[maxn],ans[maxn],p[maxn]; int n,m,tot,cnt,root,maxx; bool cmp(int a,int b) { return s[a]<s[b]; } void add(int a,int b,int c) { to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++; } void getroot(int x,int fa) { int i,mx=0; siz[x]=1; for(i=head[x];i!=-1;i=next[i]) { if(to[i]==fa||vis[to[i]]) continue; getroot(to[i],x); siz[x]+=siz[to[i]]; mx=max(mx,siz[to[i]]); } mx=max(mx,tot-siz[x]); if(maxx>mx) root=x,maxx=mx; } void getdep(int x,int fa) { p[++p[0]]=x; for(int i=head[x];i!=-1;i=next[i]) { if(to[i]==fa||vis[to[i]]) continue; s[to[i]]=s[x]+val[i],dep[to[i]]=dep[x]+1; getdep(to[i],x); } } void calc(int x,int flag) { p[0]=0,getdep(x,0); sort(p+1,p+p[0]+1,cmp); int l=1,r=p[0],i; for(;l<r;l++) { while(l<r&&s[p[l]]+s[p[r]]>m) r--; for(i=r;i>l&&s[p[l]]+s[p[i]]==m;i--) ans[dep[p[l]]+dep[p[i]]]+=flag; } } void dfs(int x) { vis[x]=1; s[x]=dep[x]=0,calc(x,1); for(int i=head[x];i!=-1;i=next[i]) { if(vis[to[i]]) continue; s[to[i]]=val[i],dep[to[i]]=1,calc(to[i],-1); maxx=1<<30,tot=siz[to[i]],getroot(to[i],x); dfs(root); } } int main() { scanf("%d%d",&n,&m); int i,a,b,c; memset(head,-1,sizeof(head)); for(i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c),a++,b++; add(a,b,c),add(b,a,c); } maxx=1<<30; getroot(1,0); dfs(1); for(i=1;i<=n;i++) { if(ans[i]) { printf("%d\n",i); return 0; } } printf("-1"); return 0; }
| 欢迎来原网站坐坐! >原文链接<