Luogu P2726 [SHOI2005]树的双中心
https://www.luogu.com.cn/problem/P2726
题面
\(N\leq 50000,H\leq 100\)
分析
考虑性质:任何一对\((x,y)\),可以把一棵树划分成两部分,其中一部分到\(x\)的距离小,另一部分到\(y\)的距离小
所以枚举每一条边,将其断掉,然后求出两个联通块的最小值加起来取\(Min\)(如果\(x\)的联通块中的点到\(y\)的距离小,则存在一种更优的划分方式)
考虑快速求最小值(重心),令\(f_u\)表示u节点的最小值
\(\forall u=Fa_v ,f_v=f_u+siz_r-2\times siz_v\)
如果\(v\)比\(u\)优,则\(siz_v\times 2>siz_r\),只可能是\(u\)中的最大儿子;否则\(u\)最优
而每次断边,只会影响点到根上的值,所以维护最大值和次大值,暴力修改即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,cnt,to[N],nxt[N],he[N],d0[N],d1[N],Fa[N],a[N];
ll siz[N],f[N],g[N],ans;
inline void add(int u,int v) {
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
void dfs(int fa,int u) {
Fa[u]=fa;
siz[u]=a[u];
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa){
dfs(u,v);
f[u]+=f[v]+siz[v];
siz[u]+=siz[v];
if(siz[d0[u]]<siz[v]) {
d1[u]=d0[u],d0[u]=v;
} else {
if(siz[d1[u]]<siz[v]) {
d1[u]=v;
}
}
}
}
}
void dgs(int u,int r) {
if(!d0[u]) {
ans+=g[u];
return;
}
if(siz[d0[u]]>=siz[d1[u]]&&siz[r]<siz[d0[u]]*2) {
g[d0[u]]=g[u]-siz[d0[u]]*2+siz[r];
dgs(d0[u],r);
return;
}
if(siz[d1[u]]>=siz[d0[u]]&&siz[r]<siz[d1[u]]*2) {
g[d1[u]]=g[u]-siz[d1[u]]*2+siz[r];
dgs(d1[u],r);
return;
}
ans+=g[u];
}
struct A{int x; ll y,z; };
vector<A>V;
int main() {
scanf("%d",&n);
for(int i=1;i<n;i++) {
int u,v; scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
dfs(0,1);
ll Ans=1e18;
for(int i=2;i<=n;i++) {
ans=0;
g[i]=f[i],dgs(i,i);
V.clear();
int u=i;
while(u!=0) {
V.push_back((A){u,siz[u],f[u]});
u=Fa[u];
}
for(int j=V.size()-1;j>0;j--) {
f[V[j].x]-=f[V[j-1].x]+siz[V[j-1].x];
siz[V[j].x]-=siz[i];
}
f[V[0].x]=0,siz[V[0].x]=0;
for(int j=1;j<V.size();j++) {
f[V[j].x]+=f[V[j-1].x]+siz[V[j-1].x];
}
g[1]=f[1],dgs(1,1);
for(int j=0;j<V.size();j++) {
f[V[j].x]=V[j].z,siz[V[j].x]=V[j].y;
}
/* printf("%lld\n",ans);
for(int j=1;j<=n;j++) {
printf("%lld ",g[i]);
}
puts("");*/
Ans=min(Ans,ans);
}
printf("%lld\n",Ans);
return 0;
}