【题解】[JOISC 2021 Day3] ビーバーの会合 2
首先 \(j\) 为奇数时答案为 \(1\) 。
简单证明,\(j\) 个点之间的重心一定是一个可期待点。
根据重心的性质,所有可能的重心一定构成一条链。
那么已知一个重心为 \(x\) ,如果要移动到一个相邻点 \(y\) ,则断开 \((x,y)\) 后两颗子树大小一定相同。
而 \(j\) 为奇数时两边一定一奇一偶,不可能相等。
同理我们可以推出对于偶数 \(j\) 的答案是最长的链的长度使得链两端的子树大小 \(\ge \dfrac{j}{2}\) 。
求所有满足条件的链中长度最大的,比较套路的做法是点分治。
我们开一个桶记录后缀最大值即可,更新答案时合并两个桶即可。
一个细节是点分治时单链可以单独作为答案,因为分治时取的是重心,所以每一个子树大小一定 \(\le \dfrac{size}{2}\) 。
时间复杂度 \(\mathcal{O}(N\log N)\) 。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 200005
using namespace std;
int n,h[N],tot,v[N],sz[N],u[N],w[N],ed,mn,cs,ans[N];
struct edge{int to,nxt;}e[N<<1];
void add(int x,int y){e[++tot].nxt=h[x];h[x]=tot;e[tot].to=y;}
void find(int x,int fa){
sz[x]=1;int cur=0;
for(int i=h[x];i;i=e[i].nxt)if(e[i].to!=fa&&!v[e[i].to])
find(e[i].to,x),cur=max(cur,sz[e[i].to]),sz[x]+=sz[e[i].to];
cur=max(cur,cs-sz[x]);
if(cur<mn)mn=cur,ed=x;
}
void dfs(int x,int fa,int dis){
sz[x]=1;
for(int i=h[x];i;i=e[i].nxt)if(e[i].to!=fa&&!v[e[i].to])
dfs(e[i].to,x,dis+1),sz[x]+=sz[e[i].to];
w[sz[x]]=max(w[sz[x]],dis);
}
void calc(int x,int wsz){
cs=mn=wsz;find(x,0);x=ed;
int ct=0;v[x]=1;
for(int i=h[x];i;i=e[i].nxt)if(!v[e[i].to]){
int y=e[i].to;
dfs(y,x,1);ct=max(ct,sz[y]);
pre(j,sz[y],1)w[j]=max(w[j],w[j+1]),ans[j*2]=max(ans[j*2],w[j]+u[j]);
rep(j,1,sz[y])u[j]=max(u[j],w[j]),w[j]=0;
}
rep(i,1,ct)u[i]=0;
for(int i=h[x];i;i=e[i].nxt)if(!v[e[i].to])calc(e[i].to,sz[e[i].to]);
}
int main(){
scanf("%d",&n);
rep(i,1,n-1){
int x,y;scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
calc(1,n);rep(i,1,n)printf("%d\n",ans[i]+1);
return 0;
}