NOIP2019 树的重心

树的重心

小简单正在学习离散数学,今天的内容是图论基础,在课上他做了如下两条笔记:

  1. 一个大小为 \(n\) 的树由 \(n\) 个结点与 \(n − 1\) 条无向边构成,且满足任意两个结点间有且仅有一条简单路径。在树中删去一个结点及与它关联的边,树将分裂为若干个子树;而在树中删去一条边(保留关联结点,下同),树将分裂为恰好两个子树。

  2. 对于一个大小为 \(n\) 的树与任意一个树中结点 \(c\),称 \(c\) 是该树的重心当且仅当在树中删去 \(c\) 及与它关联的边后,分裂出的所有子树的大小均不超过 \(\lfloor \frac{n}{2} \rfloor\)(其中 \(\lfloor x \rfloor\) 是下取整函数)。对于包含至少一个结点的树,它的重心只可能有 \(1\)\(2\) 个。

课后老师给出了一个大小为 \(n\) 的树 \(S\),树中结点从 \(1 \sim n\) 编号。小简单的课后作业是求出 \(S\) 单独删去每条边后,分裂出的两个子树的重心编号和之和。即:

\[\sum_{(u,v)\in E}\left(\sum_{x\in c(S'_u)} x+\sum_{y\in c(S'_v)} y\right) \]

上式中,\(E\) 表示树 \(S\) 的边集,\((u, v)\) 表示一条连接 \(u\) 号点和 \(v\) 号点的边。\(S'_u\)\(S'_v\) 分别表示树 \(S\) 删去边 \((u, v)\) 后,\(u\) 号点与 \(v\) 号点所在的被分裂出的子树,\(c(S)\) 表示树 \(S\) 重心的集合。

小简单觉得作业并不简单,只好向你求助,请你教教他。

\(n\leq 3\times 10^5\)

题解

根据期望的线性性(或者交换求和顺序),我们只需要求出每个点\(x\)成为了多少次重心即可。

先把\(x\)当做根,然后以删点\(y\)指代删掉\(y\)到父亲的边。如果要在儿子\(y\)子树删点,那么就要考虑\(y\)子树的大小和\(x\)除了\(y\)的其他儿子的子树大小,得出删掉的点数在某个区间\([l,r]\)内。那么DFS一遍即可得出答案。

显然我们不能枚举每个节点作为根。考虑以\(1\)为根,那么当\(y\)\(x\)的儿子时,要求DFS序在某个区间内,并且子树大小属于\([l,r]\),变成了二维数点问题。当\(y\)\(x\)的父亲时,如果删掉的点不是\(x\)的祖先,限制相同。当删掉的点是\(x\)的祖先时,情况会有不同,不过这个\(x\)\(1\)的链的信息可以在DFS的时候维护下来。

时间复杂度\(O(n\log n)\)

CO int N=3e5+10;
int n;
vector<int> to[N];
int pos[N],tim,idx[N];
int siz[N],lim[N][2];

void dfs(int x,int fa){
	pos[x]=++tim,idx[tim]=x;
	siz[x]=1,lim[x][0]=lim[x][1]=0;
	for(int y:to[x])if(y!=fa){
		dfs(y,x);
		siz[x]+=siz[y];
		if(siz[y]>lim[x][1]){
			lim[x][1]=siz[y];
			if(lim[x][1]>lim[x][0]) swap(lim[x][0],lim[x][1]);
		}
	}
	if(n-siz[x]>lim[x][1]){
		lim[x][1]=n-siz[x];
		if(lim[x][1]>lim[x][0]) swap(lim[x][0],lim[x][1]);
	}
}

namespace Seg{
	int root[N],tot;
	int lc[N*20],rc[N*20],sum[N*20];
	
	#define mid ((l+r)>>1)
	void insert(int&x,int l,int r,int p,int v){
		++tot,lc[tot]=lc[x],rc[tot]=rc[x];
		sum[tot]=sum[x]+v,x=tot;
		if(l==r) return;
		if(p<=mid) insert(lc[x],l,mid,p,v);
		else insert(rc[x],mid+1,r,p,v);
	}
	int query(int x,int l,int r,int ql,int qr){
		if(ql>qr or !x) return 0;
		if(ql<=l and r<=qr) return sum[x];
		if(qr<=mid) return query(lc[x],l,mid,ql,qr);
		if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
		return query(lc[x],l,mid,ql,qr)+query(rc[x],mid+1,r,ql,qr);
	}
	#undef mid
}

namespace Bit1{
	int sum[N];
	
	#define lowbit(x) (x&-x)
	void insert(int p,int v){
		for(int i=p;i<=n;i+=lowbit(i)) sum[i]+=v;
	}
	int query(int p){
		int ans=0;
		for(int i=p;i;i-=lowbit(i)) ans+=sum[i];
		return ans;
	}
	IN int query(int l,int r){
		if(l>r) return 0;
		return query(r)-query(l-1);
	}
	#undef lowbit
}

namespace Bit2{
	int sum[N];
	
	#define lowbit(x) (x&-x)
	void insert(int p,int v){
		for(int i=p;i<=n;i+=lowbit(i)) sum[i]+=v;
	}
	int query(int p){
		int ans=0;
		for(int i=p;i;i-=lowbit(i)) ans+=sum[i];
		return ans;
	}
	IN int query(int l,int r){
		if(l>r) return 0;
		return query(r)-query(l-1);
	}
	#undef lowbit
}

int64 solve(int x,int fa){
	int64 ans=0;
	for(int y:to[x])if(y!=fa){
		int L=max(2*siz[y]-n,1),R=n-2*(lim[x][0]==siz[y]?lim[x][1]:lim[x][0]);
		ans+=Seg::query(Seg::root[pos[y]+siz[y]-1],1,tim,L,R)-Seg::query(Seg::root[pos[y]-1],1,tim,L,R);
	}
	int L=max(2*(n-siz[x])-n,1),R=n-2*(lim[x][0]==n-siz[x]?lim[x][1]:lim[x][0]);
	ans+=Seg::query(Seg::root[pos[x]-1],1,tim,L,R);
	ans+=Seg::query(Seg::root[tim],1,tim,L,R)-Seg::query(Seg::root[pos[x]+siz[x]-1],1,tim,L,R);
	ans+=Bit2::query(L,R)-Bit1::query(L,R);
	ans*=x;
	Bit1::insert(siz[x],1);
	for(int y:to[x])if(y!=fa){
		Bit2::insert(n-siz[y],1);
		ans+=solve(y,x);
		Bit2::insert(n-siz[y],-1);
	}
	Bit1::insert(siz[x],-1);
	return ans;
}
void real_main(){
	read(n);
	for(int i=1;i<=n;++i) to[i].clear();
	for(int i=1;i<n;++i){
		int x=read<int>(),y=read<int>();
		to[x].push_back(y),to[y].push_back(x);
	}
	tim=0,dfs(1,0);
	Seg::tot=0;
	for(int i=1;i<=n;++i){
		Seg::root[i]=Seg::root[i-1];
		Seg::insert(Seg::root[i],1,n,siz[idx[i]],1);
	}
	printf("%lld\n",solve(1,0));
}
int main(){
	freopen("centroid.in","r",stdin),freopen("centroid.out","w",stdout);
	for(int T=read<int>();T--;) real_main();
	return 0;
}

posted on 2020-06-17 10:35  autoint  阅读(240)  评论(0编辑  收藏  举报

导航