樹的重心
無根樹的重心定義為:
令 \(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();
}
}