AcWing 346 走廊潑水節
阿新 • • 發佈:2022-03-25
一、做法
初始時先將每一個點看成一個大小為\(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; }