1. 程式人生 > 其它 >【題解】[SHOI2005]樹的雙中心

【題解】[SHOI2005]樹的雙中心

技術標籤:題解

題目描述
在這裡插入圖片描述
solution:
考慮如何化簡這個 m i n min min 函式。

假設所選的點為 x x x y y y,那麼從路徑 x y xy xy 的中點進行劃分,可知答案為左邊部分為到 x x x 的距離加上右邊部分到 y y y 的距離。

考慮如何理解 m i n min min 函式:可以看作選出 x x x y y y,然後對每一個點都任意選擇加上到 x x x 或到 y y y 的距離。由上可知,最優的決策一定是整個樹被一條邊分成兩顆樹後的結果。

我們可以不列舉 x x x y y y,而是列舉這個斷開的點,再分別求出兩個樹的帶權重心,把兩棵樹的花費加起來即可。

總的來說,對於 x x x y y y ,我們不能求出劃分點,但對於給定的劃分點,我們的 x x x, y y y 是固定的,而且恰好是樹的重心。

求重心可以 O ( h ) O(h) O(h)。具體做法是每次都跳重兒子。所以時間複雜度 O ( n h ) O(nh) O(nh)

#include<bits/stdc++.h>
using namespace std;
const int N=50005;
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9'
) {if(ch=='-') flag=0; ch=getchar();} while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();} if(flag) return X; return ~(X-1); } struct Edge{ int u,v; }s[N]; int head[N*2],nxt[N*2],to[N*2],p[N*2],cnt,_siz; int n,f[N],g[N],val[N],siz[N],fa[N],res,ans,sum; int g2[N]
; int son[N],son2[N];//重兒子和次重兒子,和長鏈剖分也有關係 void add(int x,int y,int z) { to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt,p[cnt]=z; } void dfs(int x,int fath) { siz[x]=val[x]; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(y==fath) continue; fa[y]=x; dfs(y,x); siz[x]+=siz[y]; f[x]+=f[y]+siz[y]; if(siz[y]>siz[son[x]]) son2[x]=son[x],son[x]=y; else if(siz[y]>siz[son2[x]]) son2[x]=y; } } void dfs2(int x,int fath) { if(x==1) g[x]=f[x]; else g[x]=g[fath]+_siz-2*siz[x]; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(y==fath) continue; dfs2(y,x); } } void dfs3(int x,int fath,int C) { if(x==0) return; ans=min(ans,g2[x]); int A=son[x],B=son2[x]; if(A!=C) { if(siz[A]>_siz/2) { g2[A]=g2[x]+_siz-2*siz[A]; dfs3(A,x,C); } } if(B!=C) { if(siz[B]>_siz/2) { g2[B]=g2[x]+_siz-2*siz[B]; dfs3(B,x,C); } } } int main() { n=read(); for(int i=1;i<n;i++) { s[i].u=read(),s[i].v=read(); add(s[i].u,s[i].v,i); add(s[i].v,s[i].u,i); } for(int i=1;i<=n;i++) val[i]=read(),sum+=val[i]; _siz=sum; dfs(1,0); dfs2(1,0); res=0x3f3f3f3f; for(int i=n-1;i>=1;i--) { int x=s[i].u,y=s[i].v,tot1,tot2; if(fa[x]!=y) swap(x,y); ans=0x3f3f3f3f; g2[x]=f[x],_siz=siz[x]; dfs3(x,y,0);//以x為子樹的帶權重心 tot1=ans; ans=0x3f3f3f3f; int T=1,z=y; while(y!=1) { siz[y]-=siz[x]; y=fa[y],T++; } siz[1]-=siz[x],g2[1]=g[1]-T*siz[x]-f[x],_siz=sum-siz[x]; dfs3(1,0,x);//以1為子樹,去掉x子樹的帶權重心 tot2=ans; res=min(res,tot1+tot2); while(z!=1) { siz[z]+=siz[x]; z=fa[z]; } siz[1]+=siz[x]; } printf("%d",res); }

這裡面有求重心的方法:https://www.cnblogs.com/knife-rose/p/11258403.html