版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
不能和其他题目重名的最小生成树
题目
知识点:最小生成树的Kruskal 算法
已知一个完全图唯一的最小生成树(即知道这个树所有边的端点和权值),其余的边权值未知,问这个完全图所有边权值和的最小值。
完全图是每对顶点之间都恰连有一条边的简单图。
输入
第一行一个整数t表示数据组数(1≤t≤10)
每组数据第一行一个正整数n,表示完全图的点数(2≤n≤105)
接下来n-1行,每行三个整数x,y,z,表示x,y之间有一条权值为z的边(无向边) (1≤x,y≤n,1≤z≤10000)
输出
每组数据一行一个整数
输入
2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5
输出
9
20
思路
比较难,需要对kruskal算法有比较好的理解。
考虑kruskal算法的过程,扫描到边(x,y,z)时,x所在的集合为Sx,y所在的集合为Sy,此时需要合并两个集合,并添加一条边,形成一颗新树。为了保证(x,y,z)一定在最后的最小生成树中,那么∀u∈Sx,∀v∈Sy,(u,v)≠(x,y),uv边的权值应该是大于z的。为了是图最后的边权值最小,uv的边权值应该为z+1
代码
#include <cstdlib>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
struct edg
{
int x, y, z;
bool operator<(const edg& r) const
{
return z < r.z;
}
};
const int ms = 1e5 + 10;
int pre[ms], s[ms];
edg e[ms];
int find(int x)
{
if (pre[x] != x)
pre[x] = find(pre[x]);
return pre[x];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--)
{
int n; ll res = 0;
cin >> n;
for (int i = 1; i < n; ++i)
{
cin >> e[i].x >> e[i].y >> e[i].z;
res += e[i].z;
}
for (int i = 1; i <= n; ++i)
{
pre[i] = i; s[i] = 1;
}
sort(e + 1, e + n);
for (int i = 1; i < n; ++i)
{
int fx = find(e[i].x), fy = find(e[i].y);
if (fx != fy)
{
pre[fx] = fy;
res += 1ll * (e[i].z + 1)*(s[fx] * s[fy] - 1);
s[fy] += s[fx];
s[fx] = 0;
}
}
cout << res << "\n";
}
return 0;
}