luogu P2726 [SHOI2005]樹的雙中心
阿新 • • 發佈:2018-11-05
強行安利->巨佬題解
如果只有一個點貢獻答案,那麼答案顯然是這棵樹的帶權重心,這個是可以\(O(n)\)求的.一個\(O(n^2)\)暴力是列舉兩個集合之間的分界邊,然後對這兩個集合分別算答案,合併更新
考慮優化此過程,一個結論是一棵樹內,只有\(size_i*2>size_{root}\)的點才有可能成為帶權重心,並且這一類點個數不超過2個 不會證啊qwq,感性理解一下吧.所以每次列舉是哪條邊為分界線,然後把樹分成兩部分,從每個樹的根開始算答案,如果\(size_i*2\le size_{root}\)就停止
注意要扣除下面子樹對上面子樹的\(size\)的貢獻再做
更多細節詳見程式碼
#include<bits/stdc++.h> #define LL long long #define il inline #define re register #define db double #define eps (1e-5) using namespace std; const int N=50000+10; il LL rd() { LL x=0,w=1;char ch=0; while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();} while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*w; } int to[N<<1],nt[N<<1],hd[N],tot=1; il void add(int x,int y) { ++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot; ++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot; } int n,fa[N],sz[N],de[N],g[N],fc[N],sc[N],no,ans=1<<30; void dfs(int x) { for(int i=hd[x];i;i=nt[i]) { int y=to[i]; if(y==fa[x]) continue; fa[y]=x,de[y]=de[x]+1,dfs(y),sz[x]+=sz[y],g[x]+=g[y]+sz[y]; if(sz[fc[x]]<=sz[y]) sc[x]=fc[x],fc[x]=y; else if(sz[sc[x]]<sz[y]) sc[x]=y; } } void cal(int x,int nw,int size,int &an) { an=min(an,nw); int y=sz[fc[x]]<sz[sc[x]]||fc[x]==no?sc[x]:fc[x]; if(sz[y]*2>size) cal(y,nw-sz[y]+size-sz[y],size,an); } void work(int x) { for(int i=hd[x];i;i=nt[i]) { int y=to[i]; if(y!=fa[x]) { no=y; for(int xx=x;xx;xx=fa[xx]) sz[xx]-=sz[y]; int aa=1<<30,bb=1<<30; cal(1,g[1]-g[y]-de[y]*sz[y],sz[1],aa),cal(y,g[y],sz[y],bb); for(int xx=x;xx;xx=fa[xx]) sz[xx]+=sz[y]; ans=min(ans,aa+bb); work(y); } } } int main() { n=rd(); for(int i=1;i<n;i++) add(rd(),rd()); for(int i=1;i<=n;i++) sz[i]=rd(); dfs(1),work(1); printf("%d\n",ans); return 0; }