首先,我们可以发现这题的可行方案具有单调性,所以可以考虑用 二分 + O ( n ) \mathcal O(n) O(n) check 来寻找最优答案
显然,我们只需要考虑从根往下走的情况下,也就是不需要考虑回头
因为在回溯的时候可以“无压力”地染色,回溯后再往下走 肯定比 直接往下走的代价小。所以,如果我们已经计算过了所有直接往下走的代价,就已经覆盖了所有的答案。
设 f i f_i fi 表示 i i i 的子树内(不包括 i i i)需要祖先来染色的节点个数,也就是说这些节点在 B 走到 i i i 的祖先的时候就需要被染色
k k k 为当前二分到的,一次操作可以染色的节点个数
转移方程: f x = { ∑ f y } + d x − k f_x=\lbrace\sum f_y \rbrace+d_x-k fx={
∑fy}+dx−k
其中 d x d_x dx 为 x x x 的子节点个数, y y y 为 x x x 的儿子
在求出 f f f 数组后,如果 f 1 = 0 f_1=0 f1=0 说明可行,否则不可行
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int Maxn=300010;
const int Maxm=Maxn<<1;
int nxt[Maxm],to[Maxm];
int head[Maxn],d[Maxn],f[Maxn];
int n,val,ans,edgecnt;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;ch=getchar();}
while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
inline void add(int x,int y)
{
++edgecnt;
nxt[edgecnt]=head[x];
to[edgecnt]=y;
head[x]=edgecnt;
}
void dfs(int x,int fa)
{
int tot=d[x]-val;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa)continue;
dfs(y,x),tot+=f[y];
}
f[x]=max(0,tot);
}
bool check()
{
memset(f,0,sizeof(f));
dfs(1,0);
return f[1]==0;
}
int main()
{
// freopen("in.txt","r",stdin);
n=read();
for(int i=1;i<n;++i)
{
int x=read(),y=read();
add(x,y),add(y,x);
++d[x],++d[y];
}
for(int i=2;i<=n;++i)
--d[i];
int l=0,r=n-1;
while(l<r)
{
int mid=(l+r)>>1;val=mid;
if(check())r=mid;
else l=mid+1;
}
printf("%d\n",l);
return 0;
}