1. 程式人生 > 其它 >AcWing 346 走廊潑水節

AcWing 346 走廊潑水節

題目傳送門

一、做法

初始時先將每一個點看成一個大小為\(1\)的連通塊,這個連通塊就可以看成一個完全圖(因為只有一個點)

\(Kruskal\)演算法,在每迴圈到一條可以合併兩個連通塊的邊\(e\)時,記\(e\)的邊長為\(w\),為了形成一個完全圖,就要使得兩個已經是完全圖的連通塊中的點有邊,但是為了使最後的唯一最小生成樹還是原來那棵而且,新增的邊一定要大於\(w\)

  • 假設新邊小於\(w\),因為新增邊後會成環,當斷開邊\(e\)形成的樹大小會變小,即不是原來那棵,所以不成立

  • 假設新邊等於\(w\),同樣的斷開\(e\),會形成一個大小一樣但結構不一樣的樹,不滿足唯一,所以也不成立。

所以只要在每次新增\(e\)的時候,給兩個連通塊內的點增加w+1長的邊即可。

證明:得出來的完全圖中的最小生成樹是原來那棵。(反證法)

假設最後生成的完全同中的最小生成樹不是原來那棵,在原樹中,從小到大遍歷\(n-1\)條邊,找出第一條不在新最小生成樹中的邊,在新樹中將它連上,會形成一個環,由之前加邊時的操作可以知道,在這個環中一定存在一條長度大於它的邊,斷開這更大條,會形成一個更小的樹,那就不是最小生成樹,所以假設不成立。

二、實現程式碼

#include <bits/stdc++.h>
using namespace std;

const int N = 6010;
// Kruskal的結構體
struct Edge {
    int a, b, w;
    bool operator<(const Edge &ed) const {
        return w < ed.w;
    }
} edge[N];

int n;
int cnt[N]; //配合並查集使用的,記錄家族人員數量
int p[N];   //並查集
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++) p[i] = i, cnt[i] = 1;
        int m = n - 1;
        //錄入n-1條邊
        for (int i = 0; i < m; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            edge[i] = {a, b, c};
        }
        //排序
        sort(edge, edge + m);

        int res = 0;
        for (int i = 0; i < m; i++) {
            auto e = edge[i];
            int a = find(e.a), b = find(e.b), w = e.w;
            if (a != b) {
                // a集合數量,b集合數量,相乘,但需要減去已經建立的最小生成權這條邊
                // w是最小的,其它的可以建立最小也得大於w,即w+1
                res += (cnt[a] * cnt[b] - 1) * (w + 1);
                p[a] = b;         //合併到同一集合
                cnt[b] += cnt[a]; // b家族人數增加cnt[a]個
            }
        }
        //輸出
        cout << res << endl;
    }
    return 0;
}