分析
对于 \(A\) 来说,最坏情况即 \(B\) 一路走到他最不好拦下的叶子结点,因为 \(B\) 不可能往回走,否则就浪费了,任 \(A\) 宰割。
对于每一个节点来说, \(A\) 需要确保两个事情,一个是保住它的所有子节点,二是用剩余的力量去提前处理它的后代们的问题。易得这是一个树形 \(DP\) ,而我们可以二分列举 \(A\) 的力量,然后用它去解决问题,判断是否能处理。
对于 \(dp\) 的转移式,我们通过上述分析可以得出对于某个节点,它对祖先们只有需要分担和不需要两种状态,不会去帮祖先分担,因此 \(dp\) 值要么为0,要么为它需要祖先帮忙分担的力量。
因此,我们可以得到核心转移式。
sum+=f[v]+1;
f[u]=max(0,sum-mid);
有了这个,最后只要得到 \(f_1=0\) ,即根节点有该条件就能完美地处理所有节点的问题,不需要某个神秘力量在帮根节点分担,说明 \(mid\) 可行,存入 \(ans\) 并更新 \(right\) ,查找更小答案,否则更新 \(left\) ,不断缩短,得出答案。
代码
#include<bits/stdc++.h>
using namespace std;
int n,f[300001],le,ri,mid,ans,head[300001],tot;
struct node{//邻接表
int to,next;
}a[600001];
void check(int u,int fr,int k){//深搜
int sum=0;
for(int i=head[u];i;i=a[i].next){
int v=a[i].to;
if(v==fr)continue;//判断父节点
check(v,u,k);
sum+=f[v]+1;//染色子节点,并帮忙分担
}
f[u]=max(0,sum-k);//如果能力超出需要,只说明不需要祖先帮忙分担,无法做出更多贡献
}
void read(int &res){
res=0;
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){res=(res<<1)+(res<<3)+c-48;c=getchar();}
}
void add(int q,int m){
a[++tot].to=m;
a[tot].next=head[q];
head[q]=tot;
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n;
for(int i=1;i<n;i++){
int x,y;
read(x);read(y);
add(x,y);
add(y,x);
}
le=0;ri=n;
while(le<=ri){
int mid=(le+ri)>>1;
check(1,0,mid);
if(f[1]==0){//可行,更新答案
ans=mid;
ri=mid-1;
}
else le=mid+1;
}
cout<<ans<<endl;
return 0;
}