#点分治,Kruskal#AT3611 Tree MST
题目
给定一棵 \(n\) 个节点的树,现有有一张完全图,
两点 \(x,y\) 之间的边长为 \(w_x+w_y+dis_{x,y}\),
其中 \(dis\) 表示树上两点的距离。
求完全图的最小生成树。
分析
考虑两两点对都会在重心的位置被统计一次,
而可以在此过程连边,只要找到当前子树\(\min\{w_x+d_x\}\)所对应的\(x\),
一共\(O(n\log_2 n)\)条边,直接跑Kruskal即可
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int N=200011;
struct node{int y,w,next;}e[N<<1];
struct rec{int x,y; long long w;}b[N<<5];
long long ans,d[N]; bool v[N];
int big[N],siz[N],a[N],SIZ,f[N],cho;
int root,n,m,as[N],tot,et=1,B[N];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
bool cmp(rec x,rec y){return x.w<y.w;}
inline signed getf(int u){return f[u]==u?u:f[u]=getf(f[u]);}
inline signed max(int a,int b){return a>b?a:b;}
inline void dfs(int x,int fa){
siz[x]=1,big[x]=0;
for (rr int i=as[x];i;i=e[i].next)
if (e[i].y!=fa&&!v[e[i].y]){
dfs(e[i].y,x);
siz[x]+=siz[e[i].y];
big[x]=max(big[x],siz[e[i].y]);
}
big[x]=max(big[x],SIZ-siz[x]);
if (big[x]<=big[root]) root=x;
}
inline void calc(int x,int fa){
B[++tot]=x;
if (a[cho]+d[cho]>a[x]+d[x]) cho=x;
for (rr int i=as[x];i;i=e[i].next)
if (!v[e[i].y]&&e[i].y!=fa)
d[e[i].y]=d[x]+e[i].w,calc(e[i].y,x);
}
inline void dp(int x){
v[x]=1,d[x]=0,cho=B[tot=1]=x;
for (rr int i=as[x];i;i=e[i].next)
if (!v[e[i].y]) d[e[i].y]=d[x]+e[i].w,calc(e[i].y,x);
for (rr int i=1;i<=tot;++i)
b[++m]=(rec){cho,B[i],a[cho]+d[cho]+a[B[i]]+d[B[i]]};
for (rr int i=as[x];i;i=e[i].next)
if (!v[e[i].y]){
big[0]=SIZ=siz[e[i].y];
dfs(e[i].y,root=0),dp(root);
}
}
signed main(){
n=iut();
for (rr int i=1;i<=n;++i) a[i]=iut(),f[i]=i;
for (rr int i=1;i<n;++i){
rr int x=iut(),y=iut(),w=iut();
e[++et]=(node){y,w,as[x]},as[x]=et;
e[++et]=(node){x,w,as[y]},as[y]=et;
}
big[0]=SIZ=n,dfs(1,root=0),dfs(root,0),dp(root);
sort(b+1,b+1+m,cmp);
for (rr int i=1;i<=m;++i){
rr int fa=getf(b[i].x),fb=getf(b[i].y);
if (fa!=fb) f[fa]=fb,ans+=b[i].w;
}
return !printf("%lld",ans);
}