D61 树的直径 二分 P3000 [USACO10DEC] Cow Calisthenics G
D61 树的直径 二分 P3000 [USACO10DEC] Cow Calisthenics G_哔哩哔哩_bilibili
P3000 [USACO10DEC] Cow Calisthenics G - 洛谷
给定一棵 n 个节点的树,让你断开 s 条边,使得 s+1 棵树的直径的最大值最小。求出这个直径的值。
思路
最大值最小,考虑二分。(二分的板子:A05【模板】二分查找 - 董晓 - 博客园)
我们二分最大直径,每次跑一遍 dfs 计算最少要断多少条边才能满足最大直径 $<=$ 二分的直径。
用 d[x] 记录节点 x 下面合法的最长链的长度。
枚举当前长度 len,考虑剪断节点 x 下面的那些超过长度 len 的链。
枚举当前节点 x 的儿子节点 y:
- 如果当前直径 $d[x]+d[y]+1<=len$,我们就用数组 $d[x]$ 保存最长链。
- 如果当前直径 $d[x]+d[y]+1>len$,我们就剪断最长链,即断边数量 $+1$,同时记录两者之中的较短链。
如图,如果 $len=3$,枚举 x 的第 2 条链时,就需要断开;枚举到第 3 条链时,不需要断开。
再向上回溯,枚举 fa[x] 的第 2 条链时,又需要断开 fa[x]~x 之间的边。

// 树的直径 二分 O(nlogn) #include<bits/stdc++.h> using namespace std; const int N=100010; int ne[N<<1],to[N<<1],h[N],idx; void add(int a,int b){ to[++idx]=b;ne[idx]=h[a];h[a]=idx; } int n,s,cnt,len,d[N]; //d[x]表示节点x下面悬挂的合法的最长链 void dfs(int x,int fa){ if(cnt>s) return; //剪枝 for(int i=h[x],y;i;i=ne[i])if((y=to[i])!=fa){ dfs(y,x); if(d[x]+d[y]+1<=len)d[x]=max(d[x],d[y]+1); //两个链长和不超过len,取长链长度 else ++cnt,d[x]=min(d[x],d[y]+1); //两个链长和超过len,长链断边,取短链长度 } } bool check(){ cnt=0; //断边次数 fill(d,d+n+1,0); //清空 dfs(1,0); return cnt<=s; //断边次数少,则len仍大,游标左移 } int main(){ scanf("%d%d",&n,&s); for(int i=1,u,v;i<n;++i){ scanf("%d%d",&u,&v); add(u,v),add(v,u); } int l=-1,r=n/s+1; while(l+1<r){ len=l+r>>1; if(check()) r=len; else l=len; } printf("%d\n",r); }
浙公网安备 33010602011771号