2019E0_J 不能和其他题目重名的最小生成树

不能和其他题目重名的最小生成树

题目

知识点:最小生成树的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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章