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 之间的边。            

                            image

// 树的直径 二分 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);
}

 

posted @ 2026-02-08 20:46  董晓  阅读(72)  评论(0)    收藏  举报