LOJ#6042「雅礼集训 2017 Day7」跳蚤王国的宰相
题目大意
一棵树,每次操作可以\(cut\)并\(link\)一次,对每个点求最少多少次操作后这个点变为重心。
题解
为了方便分析,找一个重心拉出来作为根。
考虑一个点,不难发现删掉的子树只可能是根或根的其他儿子,否则往上走不会变劣。
然后就可以随便维护了。
个人做法:
把根所有儿子的\(siz\)拉出来,二分一下,\(check\)的时候就是求挖掉一个数后前\(k\)大的和,预处理前缀和即可,详见代码。
切掉根的代价就是当前根儿子的\(siz\)减当前点的\(siz\),算答案的时候前\(mid\)大的和和前\(mid-1\)大的和+切掉根的代价取\(min\)即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
using namespace std;
int rd(){
int x=0,flg=1;
char c=getchar();
for (;(c<48||c>57)&&c!='-';c=getchar());
if (c=='-') flg=-1,c=getchar();
for (;c>47&&c<58;x=x*10+c-48,c=getchar());
return flg*x;
}
const int mxn=1000010;
int n,m,rt,num,head[mxn],siz[mxn],a[mxn],s[mxn],pos,ans[mxn];
struct ed{int to,nxt;}edge[mxn<<1];
void addedge(int u,int v){
edge[++m]=(ed){v,head[u]},head[u]=m;
edge[++m]=(ed){u,head[v]},head[v]=m;
}
void getrt(int u,int fa){
siz[u]=1;
int mx=0;
for (int i=head[u],v;i;i=edge[i].nxt)
if ((v=edge[i].to)!=fa) getrt(v,u),siz[u]+=siz[v],mx=max(mx,siz[v]);
mx=max(mx,n-siz[u]);
if (mx<=num) num=mx,rt=u;
}
int f(int x,int nm){
if (!x) return 0;
int y=x+(x>=pos);
if (a[y]<nm){
--x,y=x+(x>=pos);
return s[y]-(y>=pos)*num+nm;
}
return s[y]-(y>=pos)*num;
}
void dfs(int u,int fa){
int l=0,r=m;
for (;l<=r;)
if (n-siz[u]-f(mid,num-siz[u])<=n>>1) r=mid-1;
else l=mid+1;
ans[u]=l;
for (int i=head[u],v;i;i=edge[i].nxt)
if ((v=edge[i].to)!=fa) dfs(v,u);
}
bool cmp(const int &x,const int &y){
return x>y;
}
int main()
{
n=rd();
for (int i=1,x,y;i<n;++i)
x=rd(),y=rd(),addedge(x,y);
num=1e9,getrt(1,0);
num=1e9,getrt(rt,0);
m=0;
for (int i=head[rt];i;i=edge[i].nxt) a[++m]=siz[edge[i].to];
sort(a+1,a+m+1,cmp);
for (int i=1;i<=m;++i) s[i]=s[i-1]+a[i];
for (int i=head[rt];i;i=edge[i].nxt){
int v=edge[i].to,l=1,r=m;
for (;l<=r;)
if (a[mid]>siz[v]) l=mid+1;
else r=mid-1;
pos=l;
num=siz[v];
dfs(v,rt);
}
for (int i=1;i<=n;++i)
printf("%d\n",ans[i]);
return 0;
}