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