【題解】[SHOI2005]樹的雙中心
阿新 • • 發佈:2020-12-10
技術標籤:題解
題目描述
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