YbtOJ#662-交通运输【线段树合并,树状数组】

正题

题目链接:http://www.ybtoj.com.cn/contest/122/problem/2


题目大意

给出 n n n个点的一棵有根树,对于每个 x x x求,删除点 x x x后修改某个点的父节点(修改前该点必须有父节点)后最小化最大联通块大小。


解题思路

删掉一个点后肯定是最大的那个联通块分一个子树给最小的那个联通块。
m a x s maxs maxs表示最大的联通块, r m a x rmax rmax表示次大的联通块, m i n s mins mins表示最小的联通块,那么答案一定在 [ r m a x , m a x s ] [rmax,maxs] [rmax,maxs]之间,而且最小化最大值我们可以考虑二分

然后要分成两种情况
若最大联通块是某一个儿子的子树,这种情况很好处理,我们用线段树合并计算出每个子树可以取的子树大小(不包括它本身),然后判断一下是否有大小在 [ m a x s − m i d , m i d − m i n s ] [maxs-mid,mid-mins] [maxsmid,midmins]中的子树就好了。

若最大联通块是父节点所在联通块的子树,这种情况比较麻烦,我们需要把点分成两种,一种是在 x x x到根节点路径上的,一种是不在那个上面的。
用树状数组记录一下在 x x x到根节点路径上的子树大小,然后用总共的减去树状数组的和之前线段树那个在 x x x子树内的就是不在到根节点路径上的,这些和之前那样查询就好了。
剩下在到根路径上的节点的子树大小都减少了 s i z x siz_x sizx,我们将查询的区间右移 s i z x siz_x sizx位就好了,用刚刚那个树状数组就好了

时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e5+10;
struct node{
	int to,next;
}a[N];
int n,tot,fa[N],deg[N],ls[N],rt[N],siz[N],ans[N];
struct SegTree{
	int w[N<<5],ls[N<<5],rs[N<<5],cnt;
	int Change(int x,int L,int R,int pos){
		int p=++cnt;w[p]=w[x]+1;
		if(L==R)return p;int mid=(L+R)>>1;
		if(pos<=mid)ls[p]=Change(ls[x],L,mid,pos),rs[p]=rs[x];
		else rs[p]=Change(rs[x],mid+1,R,pos),ls[p]=ls[x];
		return p;
	}
	int Ask(int x,int L,int R,int l,int r){
		if(!x)return 0;
		if(L==l&&R==r)return w[x];int mid=(L+R)>>1;
		if(r<=mid)return Ask(ls[x],L,mid,l,r);
		if(l>mid)return Ask(rs[x],mid+1,R,l,r);
		return Ask(ls[x],L,mid,l,mid)+Ask(rs[x],mid+1,R,mid+1,r);
	}
	int Merge(int x,int y){
		if(!x||!y)return x+y;
		int p=++cnt;w[p]=w[x]+w[y];
		ls[p]=Merge(ls[x],ls[y]);
		rs[p]=Merge(rs[x],rs[y]);
		return p;
	}
}T;
struct TreeBinary{
	int t[N];
	void Change(int x,int val){
		while(x<=n){
			t[x]+=val;
			x+=lowbit(x);
		}
		return;
	}
	int Ask(int x){
		int ans=0;
		while(x){
			ans+=t[x];
			x-=lowbit(x);
		}
		return ans;
	}
}B,D;
void addl(int x,int y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void dfs(int x){
	siz[x]=1;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;dfs(y);siz[x]+=siz[y];
		rt[x]=T.Change(rt[x],1,n,siz[y]);
		rt[x]=T.Merge(rt[x],rt[y]);
	}
	D.Change(siz[x],1);
	return;
}
int epe(int L,int R,int x){
	if(L>R)return 0;
	return D.Ask(R)-D.Ask(L-1)+B.Ask(R+siz[x])-B.Ask(L+siz[x]-1)-T.Ask(rt[x],1,n,L,R);
}
void work(int x){
	if(x==36449)
		x++,x--;
	if(deg[x]<2){ans[x]=max(siz[1]-siz[x],siz[a[ls[x]].to]);return;}
	int p=0,mins=n,maxs=0,rmax=0;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(siz[y]>maxs)rmax=maxs,maxs=siz[y],p=y;
		else if(siz[y]>rmax)rmax=siz[y];
		mins=min(mins,siz[y]);
	}
	if(siz[1]-siz[x]>maxs){
		rmax=maxs;maxs=siz[1]-siz[x];
		mins=min(mins,siz[1]-siz[x]);
		int L=rmax,R=maxs-1;
		while(L<=R){
			int mid=(L+R)>>1;
			int l=maxs-mid,r=mid-mins;
			if(epe(l,r,x))R=mid-1;
			else L=mid+1;
		}
		ans[x]=L;return;
	}
	else if(fa[x]){
		if(siz[1]-siz[x]>rmax)rmax=siz[1]-siz[x];
		mins=min(mins,siz[1]-siz[x]);
	}
	int L=rmax,R=maxs-1;
	while(L<=R){
		int mid=(L+R)>>1;
		int l=maxs-mid,r=mid-mins;
		if(T.Ask(rt[p],1,n,l,r))R=mid-1;
		else L=mid+1;
	}
	ans[x]=L;
	return;
}
void calc(int x){
	D.Change(siz[x],-1);
	work(x);
	B.Change(siz[x],1);
	for(int i=ls[x];i;i=a[i].next)
		calc(a[i].to);
	B.Change(siz[x],-1);
	D.Change(siz[x],1);
}
int main()
{
	freopen("traffic.in","r",stdin);
	freopen("traffic.out","w",stdout);
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		scanf("%d",&fa[i]);
		deg[fa[i]]++;deg[i]++;
		addl(fa[i],i);
	}
	dfs(1);D.Change(siz[1],-1);
	work(1);B.Change(siz[1],1);
	for(int i=ls[1];i;i=a[i].next)
		calc(a[i].to);
	for(int i=1;i<=n;i++)
		printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值