樹的重心

dengchengyu發表於2024-11-26

樹的重心

無根樹的重心定義為:

\(x\) 為樹根,有 \(y\)\(x\) 相鄰,使得 \(y\) 的子樹大小的最大值最小,這樣的 \(x\) 即樹的重心。

重心有 1 個 或 2 個,若有 2 個則這 2 個重心相鄰。

性質

以重心為根,任意一個它的兒子的子樹大小不超過 \(n/2\)

樹中所有點到一個點的距離和中,到重心的最小。

根據性質,我們可以得出:

如果有一條邊 \((x,y)\),將這條邊割開,若 \(x\) 的子樹大小大於 \(y\) 的子樹大小,則重心在子樹 \(x\) 中,反之在 \(y\) 中,若相等,則 \(x,y\) 恰好是兩個重心。

同時,若在樹上新增一個葉子,則重心最多移動一條邊。

求法

掃一遍樹求子樹大小,根據定義即可求。

假定一個根 \(rt\),則一個點的值即 \(\max(sz_{son_x},sz_{rt}-sz_x)\),這個值最小點就是重心。

應用

點分治:

由於重心的子樹大小不超過一半,則可以把樹按重心分開,再把分開的每個連通子樹遞迴處理,最多分 \(O(\log n)\) 層。

則所有重心的子樹大小和是 \(O(\log n)\) 的,我們在這些重心上可以方便地計算路徑等資訊。

帶權重心

每個點有權值,將上面定義中的子樹大小換為權值和即為帶權重心的定義。

依舊有性質:

如果有一條邊 \((x,y)\),將這條邊割開,若 \(x\) 的子樹大小大於 \(y\) 的子樹大小,則重心在子樹 \(x\) 中,反之在 \(y\) 中。

若相等則需依照 \(x,y\) 的權值判斷重心,相等則都為重心,否則誰大誰為重心。

可看出:若點權都不相等,則重心唯一。

根據性質動態維護區間修改權值的帶權重心 \(O(d\log ^2n)\)

我們上面的性質結合點分治的思想:

先求出點分治的那些重心,從整顆樹的重心開始,考慮移動。

若有子樹滿足性質,顯然最多一個子樹滿足性質,則移動到連通子樹的重心(無權重心)。

若沒有,則當前點就是帶權重心。

如果可以 \(O(\log n)\) 查詢子樹權值和,則可以 \(O(d \log^2n)\) 得到重心,其中 \(d\) 為點的度數。

參考例題:[ZJOI2015 幻想鄉戰略遊戲]([P3345 ZJOI2015] 幻想鄉戰略遊戲 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn))

這道題找到重心後用動態點分治即可計算答案。

帶權重心的神奇性質

與距離結合

從樹上的任意一點作為起點,到樹上所有點的距離乘上的終點點權,使它和最小的起點一定是樹的帶權重心。

值得一提的是如果乘的是 \(dist^k\) 依然成立。

證明

當前在重心 \(x\) 上,則若有一條邊 \((x,y)\),設 \(x\) 側權值和為 \(s_x\)\(y\) 側為 \(s_y\)

考慮轉移到 \(y\),則多的貢獻為 \(s_x-s_y\),由於重心,\(s_x\ge s_y\),因此 \(y\) 不優。

對於 \(dist^k\) 的情況可感性理解。

與 dfs 序上的帶權中點結合

給定根,這裡的重心是深度最小的重心。

首先建 dfs 序。

則將節點按 dfs 排列,這上面的帶權中點在重心的子樹內。

帶權中點:將序列上權值求字首和,第一個大於總權值和一半的點。

證明

深度最小的重心 \(G\) 的子樹和 \(sz_G\) 大於 \(sum/2\)

考慮 dfs 序 \(dfn\)\(G\) 之前的點的權值和,一定小於 \(sum/2\)

同時考慮 \(dfn\)\(G\) 之後且不在 \(G\) 子樹內的點的權值和,也一定小於 \(sum/2\)

帶權中點不在前也不在後,而在中間的子樹內。

用帶權中點 \(O(\log^2n)\) 動態維護區間修改權值的帶權重心

同樣的,這裡的重心是深度最小的重心。

我們的區間修改權值顯然是需要樹剖的,我們的 \(dfn\) 要在樹剖上建。

根據性質,找到帶權中點,可以線上段樹上二分,\(O(\log n)\)

找到帶權中點後,考慮倍增跳它的父親。

由於此時我們所求的重心是子樹大小 \(sz>sum/2\) 中深度最大的點,

於是考慮像倍增 LCA 那樣,找到最遠的在重心下面的點,重心就是它的父親。

對於一個點 \(x\)\(sz_x\le sum/2\),則它在我們的重心下面,於是往上跳。

最後取 \(fa_{x,0}\) 即可。

注意特判一下如果最開始的點 \(sz>sum/2\),則它就是重心。

我們每次 \(O(\log n)\) 求子樹大小,跳 \(O(\log n)\) 次,於是就是 \(O(\log^2n)\)

例題:JZOJ 7469 資料結構

本題程式碼:

//#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
namespace IO{
	const int sz=1<<22;
	char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
	inline char gc(){
		return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
	}
	template<class T> void read(T& x){
		x=0; char c=gc();
		for(;c<'0'||c>'9';c=gc());
		for(;c>='0'&&c<='9';c=gc())
			x=x*10+(c-'0');
	}
	inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
	inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
	template<class T> void write(T x,char c='\n'){
		if(x<0) pc('-'), x=-x;
		if(x==0) pc('0'); int t=0;
		for(;x;x/=10) p[++t]=x%10+'0';
		for(;t;--t) pc(p[t]); pc(c);
	}
	struct F{~F(){flush();}}f;
}
using IO::read;
using IO::write;
const int N=3e5+5;
int total,to[N*2],st[N],nx[N*2];
void add(int u,int v){
	to[++total]=v,nx[total]=st[u],st[u]=total;
}
int dfn[N],dfn1,top[N],f[N],son[N],sz[N],dep[N],lst[N];
int sz1[N],idfn[N];
#define ll long long
ll sum;
int fa[N][20];
void dfs1(int x,int y){
	sz[x]=1,dep[x]=dep[y]+1,f[x]=y,fa[x][0]=y;
	for(int i=1;i<20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=st[x];i;i=nx[i]){
		int v=to[i];
		if(v!=y){
			dfs1(v,x);
			sz[x]+=sz[v];
			if(sz[v]>sz[son[x]])son[x]=v;
		}
	}
	sz1[x]=sz[x];
}
void dfs2(int x){
	dfn[x]=++dfn1,idfn[dfn1]=x;
	if(son[f[x]]==x)top[x]=top[f[x]];
	else top[x]=x;
	if(son[x])dfs2(son[x]);
	for(int i=st[x];i;i=nx[i]){
		int v=to[i];
		if(v!=f[x]&&v!=son[x])dfs2(v);
	}
	lst[x]=dfn1;
}
#define ls (x<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
//	long long COUNT_SUM=0;
//	int TIMES=0;
//	void COUNT(){
//		COUNT_SUM++;
//	}
//	void COUNT_TIMES(){
//		TIMES++;
//	}
//	void PRINT_SUM(){
//		printf("THE SUM : %lld.%04lld W ___%lld___\n",COUNT_SUM/10000,COUNT_SUM%10000,COUNT_SUM/TIMES);
//	}
struct tree{
	ll s[N*4],tag[N*4];
	void down(int x,int l,int r){
//		COUNT();
		if(tag[x]){
			s[x]+=(ll)tag[x]*(r-l+1);
			if(l!=r)tag[ls]+=tag[x],tag[rs]+=tag[x];
			tag[x]=0;
		}
	}
	void update(int x,int l,int r,int L,int R,int y){
//		COUNT();
		if(L<=l&&r<=R){
			tag[x]+=y;
			down(x,l,r);
			return;
		}
		down(x,l,r);
		if(R<l||r<L)return;
		update(ls,l,mid,L,R,y),update(rs,mid+1,r,L,R,y);
		s[x]=s[ls]+s[rs];
	}
	ll query(int x,int l,int r,int L,int R){
//		COUNT();
		if(r<L||R<l)return 0;
		down(x,l,r);
		if(L<=l&&r<=R)return s[x];
		return query(ls,l,mid,L,R)+query(rs,mid+1,r,L,R);
	}
	int querymid(int x,int l,int r,ll p){
//		COUNT();
		down(x,l,r);
		if(s[x]<p)return 0;
		if(l==r)return l;
		int t=querymid(ls,l,mid,p);
		if(t)return t;
		return querymid(rs,mid+1,r,p-s[ls]);
	}
}tr;
#undef mid 
int n,Q;
int main(){
	freopen("yyl.in","r",stdin);
	freopen("yyl.out","w",stdout);
	read(n);
	for(int i=1;i<n;i++){
		int u,v;read(u),read(v);
		add(u,v),add(v,u);
	}
	dfs1(1,0),dfs2(1);
	read(Q);
	while(Q--){
		int opt; read(opt);
		if(opt==1){
			int x,w;
			read(x),read(w);
			sum+=(ll)sz1[x]*w;
			tr.update(1,1,n,dfn[x],lst[x],w);
		}
		else {
			int u,v,w;
			read(u),read(v),read(w);
			while(top[u]!=top[v]){
				if(dep[top[u]]<dep[top[v]])swap(u,v);
				tr.update(1,1,n,dfn[top[u]],dfn[u],w);
				sum+=(dfn[u]-dfn[top[u]]+1)*(ll)w;
				u=f[top[u]];
			}
			if(dep[u]>dep[v])tr.update(1,1,n,dfn[v],dfn[u],w);
			else tr.update(1,1,n,dfn[u],dfn[v],w);
			sum+=(abs(dfn[u]-dfn[v])+1)*(ll)w;
		}
		int as=tr.querymid(1,1,n,(sum+1)/2);
		as=idfn[as];
		for(int i=19;i>=0;i--){
			if(fa[as][i]){
				ll s1=tr.query(1,1,n,dfn[fa[as][i]],lst[fa[as][i]]);
				if(s1<=sum/2)as=fa[as][i];
			}
		}
		if(tr.query(1,1,n,dfn[as],lst[as])>sum/2)write(as);
		else write(fa[as][0]);
//		COUNT_TIMES(),PRINT_SUM();
	}
}

相關文章