1. 程式人生 > 實用技巧 >走廊潑水節---最小生成樹的簡單應用

走廊潑水節---最小生成樹的簡單應用

題目:走廊潑水節

網址:https://www.acwing.com/problem/content/348/

給定一棵\(N\)個節點的樹,要求增加若干條邊,把這棵樹擴充為完全圖,並滿足圖的唯一最小生成樹仍然是這棵樹。

求增加的邊的權值總和最小是多少。

注意: 樹中的所有邊權均為整數,且新加的所有邊權也必須為整數。

輸入格式

第一行包含整數\(t\),表示共有\(t\)組測試資料。

對於每組測試資料,第一行包含整數\(N\)

接下來\(N-1\)行,每行三個整數\(X,Y,Z\),表示\(X\)節點與\(Y\)節點之間存在一條邊,長度為\(Z\)

輸出格式
每組資料輸出一個整數,表示權值總和最小值。

每個結果佔一行。

資料範圍

1≤N≤6000
1≤Z≤100

輸入樣例:

2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5 

輸出樣例:

4
17

一道特別神奇的圖論題。(僅限對ZZL這種蒟蒻)
大概就是最初,我簡化了整個問題。考慮兩種情況:鏈和菊花圖。
發現那個菊花圖可以用貢獻做掉,但那個鏈始終不會做。

lyd老師的解法徹底震驚了我。
對於樹有什麼性質?有\(n\)個結點,每個結點到其他結點有且僅有一條簡單路徑。
等一會兒,這說明什麼??這恰恰說明對於任意一條邊,都是很重要的。切斷它們會使整棵樹分解。
等價於,一條邊,左邊的結點所在的聯通分量和右邊的結點所在的聯通分量僅通過該邊相連。
如下圖:

左右兩個聯通塊是因為中間的邊相連。

考慮上圖中從左聯通分量向右右端連,會形成一個個環,為了使該樹的邊不被替代,添的邊必須大於換上任意一條屬於原圖的邊權。
為了使演算法更高效,我們不妨先把邊從小到大排序。使用並查集來維護聯通塊即可。

程式碼如下:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn = 6000 + 10;
struct edge
{
	int u, v, w;
	bool operator <(const edge& lhs)
	{
		return w < lhs.w;
	}
} e[maxn];
int n, fa[maxn], size[maxn], ans = 0;
int get(int x)
{
	if(fa[x] == x) return x;
	return fa[x] = get(fa[x]);
}
void merge(int x, int y)
{
	int v1 = get(x), v2 = get(y);
	if(size[v1] < size[v2]) swap(v1, v2);
	fa[v2] = v1;
	size[v1] += size[v2];
	return;
}
int main()
{
	int t;
	scanf("%d", &t);
	while(t --)
	{
		scanf("%d", &n);
		for(int i = 1; i <= n; ++ i) fa[i] = i, size[i] = 1;
		for(int i = 1; i < n; ++ i)
		{
			scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);
		}
		ans = 0;
		sort(e + 1, e + n);
		for(int i = 1; i < n; ++ i)
		{
			int x, y, z;
			x = e[i].u, y = e[i].v, z = e[i].w;
			ans += (size[get(x)] * size[get(y)] - 1) * (z + 1);
			merge(x, y);
		}
		printf("%d\n", ans);
	}
	return 0;
}