[南海云课堂] [kruskal] [贪心] 走廊泼水节
posted on 2023-08-18 11:13:24 | under 题集 | source
题意
给定一棵 \(N\) 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。
注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。
\(1≤N≤6000\),\(1≤Z≤100\)
思路
主要考察了对 \(\rm{kruskal}\) 算法底层原理的理解运用。
不妨思考怎么加边才能使原最小生成树唯一。
回顾 \(\rm{kruskal}\) 的过程:从小到大取 \(n-1\) 条较小边,并使得每次得到的边的两个端点属于不同连通块。
那么最简单的加边方式就是在同一个连通块内连边,为了保证加边后还是原来的最小生成树,还需使得这些边的边权大于最近一次合并这个连通块的边的边权。
回到 \(\rm{kruskal}\),我们每次取的边是 \(e(u,v)\),令其权值为 \(w\)。由于先前的循环已经使得 \(u,v\) 所在连通块 \(U,V\) 都为完全图,那么实际上就是连接了 \(U,V\) 这两个完全图并将其合并为 \(P\),我们只需计算新加的边是多少即可。
那么新加的边数 \(tot=siz(P)-siz(U)-siz(V)-1\),注意 \(e(u,v)\) 不能被算入。于是使 \(P\) 变为完全图的最小花费就是 \(tot*(w+1)\),注意要使得原生成树唯一所以边权不能等于 \(w\)。
至于 \(siz\) 数组,在并查集合并、查询的过程中维护即可。
代码
好累啊,其实放代码里就两句话的事。
#include<bits/stdc++.h>
using namespace std;
const int N=6e3+5;
int t,n,u,v,w,ans;
int fa[N],siz[N];
struct edge{
int u,v,w;
}e[2*N];
inline int find(int k){
if(fa[k]==k){
return k;
}
return fa[k]=find(fa[k]);
}
inline int get(int k){
return k*(k-1)/2;
}
int main(){
cin>>t;
while(t--){
ans=0;
scanf("%d",&n);
for(int i=1; i<=n;i++){
fa[i]=i;
siz[i]=1;
}
for(int i=1; i<=n-1;i++){
scanf("%d%d%d",&u,&v,&w);
e[i]={u,v,w};
}
sort(e+1,e+n,[](edge a,edge b){return a.w<b.w;});
for(int i=1; i<=n-1;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
int fu=find(u),fv=find(v);
ans+=(get(siz[fu]+siz[fv])-get(siz[fu])-get(siz[fv])-1)*(w+1); //计算答案
fa[fu]=fv;
siz[fv]+=siz[fu]; //维护 siz
}
printf("%d\n",ans);
}
return 0;
}

浙公网安备 33010602011771号