( ext{Bi Bi Time!})
那是一天之前,亲爱的教练为我们拉了一套提高组难度题目,无人上 (100 ext{pts})。
( ext{Solution})
首先有个结论:被钦定节点的所有子树大小不超过 $lfloor frac{n}{2} floor $。
这个结论来源一个寻找最优解的思路:假设 (v,u) 之间有一条边,我们可以把以 (u) 为根节点的树除去 (v) 这棵子树的一团子树称为左子树((x)),把以 (v) 为根节点的树除去 (u) 这棵子树的一团子树称为右子树((y))。
那么假设我们求出 (u) 的距离和是 (dis),(v) 的距离和就是 (dis+(size_x+1)-(size_y+1)=dis+size_x-size_y)。那么如果现在有 (size_x<size_y),(v) 是会比 (u) 更优的,一直这样走下去,发现当 (size_x=size_y) 时再走就会更劣。此时的 (u,v) 就是最优解(当然这是偶数的情况,奇数就只有一个是最优解)。
所以结论得证。
那问题就转化成切多少刀可以使此点的每个子树大小不超过 $lfloor frac{n}{2}
floor $。好吧到这一步我还是不会做
贪心地想,我们砍边的花费都是一样的,每次肯定尽量砍 (size) 最大的子树(至于子树的大小限制我们就直接怼给子树自己来做就行了)。
定义 (f[u]) 为使 以 (u) 为根的子树的大小不超过 $lfloor frac{n}{2} floor $ 的最小砍次数,(tot[u]) 为砍完之后还剩多少个点。
注意这里子树 (f[u_{son}]) 的最优解一定会贡献给 (f[u]),因为就算把整棵子树 (u_{son}) 砍下来,如果不能维持其大小不超过 $lfloor frac{n}{2} floor $ 也是白搞。
我们先只管子树的情况做一遍 ( ext{DFS}),关于上面父亲那一坨的情况就是换根 ( ext{DP})。
第一遍 ( ext{DFS}) 能得到根节点的答案,后面的 ( ext{DP}) 就是需要维护父亲那一坨大小不超过 $lfloor frac{n}{2} floor $,把父亲的 (tot[fa_{son}]) 和父亲的父亲那一坨剩下的 (tot) 排个序,依次选取即可,不过因为对于父亲的每个儿子都要排除自己的 (tot),所以像第一遍 ( ext{DFS}) 的直接线性选取不行,做个前缀和二分即可。
总时间复杂度 (mathcal O(n log n))。
( ext{Code})
#include <cstdio>
#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
template <class T> inline T Swap(T &x,T &y) {x^=y^=x^=y;}
#include <algorithm>
using namespace std;
const int maxn=1e6+5;
int n,head[maxn],nxt[maxn<<1],to[maxn<<1],cnt,f[maxn],tot[maxn],lim,ans[maxn],pre[maxn],fa_f[maxn],fa_tot[maxn];
struct node {
int x,y;
} p[maxn];
void addEdge(int u,int v) {
nxt[++cnt]=head[u],to[cnt]=v,head[u]=cnt;
nxt[++cnt]=head[v],to[cnt]=u,head[v]=cnt;
}
bool cmp(node a,node b) {
return a.x<b.x;
}
void dfs1(int u,int fa) {
tot[u]=1;
erep(i,u) {
if(v==fa) continue;
dfs1(v,u);
f[u]+=f[v],tot[u]+=tot[v];
}
if(tot[u]<=lim) return;
int Cnt=0;
erep(i,u) if(v^fa) p[++Cnt].x=tot[v];
sort(p+1,p+Cnt+1,cmp);
while(tot[u]>lim) tot[u]-=p[Cnt--].x,++f[u];
}
void dfs2(int u,int fa) {
ans[u]=fa_f[u]; int Cnt=0,l,r,mid,ToT,F,res;
erep(i,u) if(v^fa) ans[u]+=f[v],p[++Cnt]=(node){tot[v],v};
p[++Cnt]=(node){fa_tot[u],fa};
sort(p+1,p+Cnt+1,cmp);
rep(i,1,Cnt) pre[i]=pre[i-1]+p[i].x;
rep(i,1,Cnt) {
if(p[i].y==fa) continue;
l=1,r=Cnt;
while(l<=r) {
mid=l+r>>1;
ToT=pre[mid]+1,F=mid; // 枚举 mid 个可以剩下,其中 +1 是 u(因为对于 u 的儿子,把 (u,v) 砍了显然没有任何意义)
if(i<=mid) ToT-=p[i].x,--F;
if(ToT<=lim) res=mid,l=mid+1;
else r=mid-1;
}
ToT=pre[res]+1,F=res;
if(i<=res) ToT-=p[i].x,--F;
fa_tot[p[i].y]=ToT,fa_f[p[i].y]=ans[u]-f[p[i].y]+Cnt-F-1;
}
erep(i,u) if(v^fa) dfs2(v,u);
}
int main() {
int u,v;
n=read(9); lim=n>>1;
rep(i,1,n-1) {
u=read(9),v=read(9);
addEdge(u,v);
}
dfs1(1,0); dfs2(1,0);
rep(i,1,n) print(ans[i],'
');
return 0;
}