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;
}
posted @ 2020-01-13 07:22  _lhyyy  阅读(160)  评论(0编辑  收藏  举报