樹上一節點最大子樹的節點數最小;c++
1.刪除重心後所得的全部子樹,節點數不超過原樹的1/2,一棵樹最多有兩個重心;數組
2.樹中全部節點到重心的距離之和最小,若是有兩個重心,那麼他們距離之和相等;優化
3.兩個樹經過一條邊合併,新的重心在原樹兩個重心的路徑上;spa
4.樹刪除或添加一個葉子節點,重心最多隻移動一條邊;3d
求解方法多種多樣,分別用到不一樣的定義和性質:
code
siz [ i ]表示 i 節點的子樹大小 dp [ i ]表示以 i 爲根節點的最大子樹大小,val[ i ]爲i節點的點權,代碼通俗易懂不過多解釋了blog
inline void dfs(int now,int fa) { siz[now]=val[now]; dp[now]=0; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs(t,now); siz[now]+=siz[t]; dp[now]=max(dp[now],siz[t]); } dp[now]=max(dp[now],n-dp[now]); if(dp[now]<dp[ans]) ans=now; }
通常來講定義求解就夠用了,但在某些時候性質求解更方便實用;get
根據性質2:咱們能夠處理出全部節點到某一節點的距離,取最小值;it
怎麼求出每一個節點到某一節點的距離呢?在dfs過程當中向下處理是很容易的,因此咱們能夠先處理出全部節點到根節點的距離qwq ;io
siz [ i ]同上,f [ i ] 表示節點 i 的全部子節點到 i 的距離和,val[ i ] 同上, a [ i ].val 爲邊權,設定1 號節點爲根節點;
inline void dfs1(int now,int fa,int deep) { siz[now]=val[now]; dep[now]=deep; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs1(t,now,deep+a[i].val); siz[now]+=siz[t]; f[now]+=f[t]+siz[t]*a[i].val; } }
對於f數組的處理的理解:t的全部子節點到t的距離+now的當前子樹全部節點到now的距離。
這樣就求得了根節點的距離和,咱們再經過根節點遞推其餘節點的距離和,有以下公示:
f [ now ] = f [ fa ] +( siz [ 根節點 ] - 2 * siz [ now ])* 邊權;(now!= 根節點)
理解以下:
對於now的子節點,每一個節點的距離減小了一個邊權,總距離減小 siz [ now ] * 邊權 ,對於非v子節點,每一個節點距離增長了一個邊權,總距離增長(siz[ 根 ]-siz [ now ])*邊權
inline void dfs2(int now,int fa) { if(now^root) f[now]=f[fa]+siz[1]-2*siz[now]; if(f[now]<sum) res=now,sum=f[now]; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs2(t,now); } }
這種方法還能夠優化:觀察式子:顯然一個節點的全部子樹中,只有子節點數最多的一個可能成爲重心,因此咱們能夠加以改進,在dfs2中只走子樹節點最多的一個:
這樣複雜度總體雖然仍是O(n)的,可是查詢複雜度變爲了O(樹高)在某些題目中(下面例題中qwq)有奇效。
inline void dfs1(int now,int fa,int deep) { siz[now]=val[now]; dep[now]=deep; int maxson=-1;//新 加
for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs1(t,now,deep+a[i].val); siz[now]+=siz[t]; f[now]+=f[t]+siz[t]*a[i].val; if(siz[t]>maxson) maxson=siz[t],son[now]=t;//新 加
} } inline void dfs2(int now,int fa) { if(now^1) f[now]=f[fa]+siz[1]-2*siz[now]; if(f[now]<sum) res=now,sum=f[now]; if(son[now]) dfs2(t,now);//改 動
}
第一行爲N,1<N<=50000,表示樹的節點數目,樹的節點從1到N編號。 接下來N-1行,每行兩個整數U,V,表示U與V之間有一條邊。 再接下N行,每行一個正整數,其中第i行的正整數表示編號爲i的節點權值爲W(I),樹的深度<=100
分析:
應該沒有黑題難度,紫色差很少。
先考慮暴力枚舉x,y,那麼對於每一對x,y分界都是一條樹上的邊。那麼咱們不如枚舉斷邊,再找出重心qwq;
先O(n)求出f [ root ] 的值,枚舉斷邊,再經過上述第二種優化過的方法求距離和,總複雜度O(N*樹高);
對於優化的處理:因爲須要斷邊,每次斷邊後最大子樹可能變小,因此咱們須要維護一個次大子樹;
#include<bits/stdc++.h>
using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } const int inf=1e18; int n,res,cut; int val[100010],siz[100010],f[100010],dep[100010]; int from[100010]; int son1[100010],son2[100010]; int head[100010],cnt=1; struct point { int nxt,to; }a[100010]; inline void add(int x,int y) { a[++cnt].nxt=head[x]; a[cnt].to=y; head[x]=cnt; } inline void dfs1(int now,int fa,int deep) { siz[now]=val[now]; dep[now]=deep; from[now]=fa; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs1(t,now,deep+1); siz[now]+=siz[t]; f[now]+=(f[t]+siz[t]); if(siz[t]>siz[son1[now]]) { son2[now]=son1[now]; son1[now]=t; } else if(siz[t]>siz[son2[now]]) { son2[now]=t; } } } inline void dfs3(int now,int sum,int all,int &ans) { ans=min(ans,sum); int t=son1[now]; if(t==cut||siz[son2[now]]>siz[son1[now]]) t=son2[now]; if(!t) return ; if(2*siz[t]>all) dfs3(t,sum+all-2*siz[t],all,ans); } inline void dfs2(int now) { for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==from[now]) continue; cut=t; for(int x=now;x;x=from[x]) siz[x]-=siz[t]; int A=inf,B=inf; dfs3(1,f[1]-f[t]-dep[t]*siz[t],siz[1],A); dfs3(t,f[t],siz[t],B); res=min(res,A+B); for(int x=now;x;x=from[x]) siz[x]+=siz[t]; dfs2(t); } } signed main() { n=read(); for(int x,y,i=1;i<n;++i) { x=read(),y=read(); add(x,y); add(y,x); } for(int i=1;i<=n;++i) { val[i]=read(); } res=inf; dfs1(1,0,0); dfs2(1); printf("%lld\n",res); return 0; }