POJ 1679 The Unique MST(演算法導論23-1次優最小生成樹)
只需要講解演算法導論的題即可。
23-1次優最小生成樹
a.
最小生成樹唯一性證明:
已知當前構造的邊集A是最小生成樹的子集。令無向圖G的一個切割是,顯然該切割是尊重A的。已知跨越該切割的輕量級邊對於A是安全的,又因為該無向圖G的每條邊的權值都不相同,所以對於當前A而言,安全邊有且只有一條,即對於每個狀態下的A,構造最小生成樹的方式是唯一的。所以最小生成樹是唯一的。
次優最小生成樹不唯一性證明:
如上圖:{(C, D), (A, D), (A, B)} 和 {(C, D), (A, C), (B, D)} 是兩個次優最小生成樹,權值和都是8。
b.
①如果最小生成樹T刪去一條邊,就必然要新增另一條邊,否則不能形成一個連通塊
②如果最小生成樹T和次小生成樹有兩條邊不同,即T' = T - {(u1, v1)} + {(x1, y1)} - {(u2, v2)} + {(x2, y2)},則可以構造出一棵和最小生成樹只有一條邊不同的生成樹T'' = T - {(u1, v1)} + {(x1, y1)},使得w(T) < w(T'') < w(T')。這和T'是次小生成樹矛盾,所以次小生成樹和最小生成樹只有一條邊不同。
③由①②可知,圖G包含邊(u, v)屬於T和邊(x, y)不屬於T,使得T - {(u, v)} + {(x, y)}是G的一棵次小生成樹。
c. 假設當前已經構造的最小生成樹的子集為A,維護
計算公式為:max(k, v) = max(max(k, u), G[u][v])
d. 演算法:假設當前求出的最小生成樹為T,列舉所有不屬於T的邊(u, v),向T中新增(u, v)。
因為會形成環,所以要刪掉一條邊。因為我們希望得到的生成樹權值最小,所以要 刪掉環中權值最大的邊,也就是max_edge(u, v),然後就會得到新的生成樹T'。在得到
程式碼如下(POJ1679):
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAX_N = 500;
const int INF = 0x3f3f3f3f;
// used[i][j] = 1表示最小生成樹中包含(i, j)這條邊。
int used[MAX_N][MAX_N];
// 題目中輸入的圖
int G[MAX_N][MAX_N];
// max_edge[i][j]表示在最小生成樹中i到j的唯一簡單路徑中權值最大的邊長。
int max_edge[MAX_N][MAX_N];
// 標記i是否使用過
int vis[MAX_N];
// mincost[i]表示i到已構造的最小生成樹子集的最短距離。
int mincost[MAX_N];
// (pre[i], i)為i到已構造的最小生成樹子集的最短邊。
int pre[MAX_N];
// n為頂點數,m為邊數。
int n, m;
// ans1為最小生成樹的權值和,ans2為次小生成樹的權值和。
int ans1, ans2;
// 初始化
void init()
{
memset(vis, 0, sizeof(vis));
memset(used, 0, sizeof(used));
memset(mincost, INF, sizeof(mincost));
memset(max_edge, 0, sizeof(max_edge));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
G[i][j] = (i == j) ? 0 : INF;
}
// 求最小生成樹的權值和
int MST()
{
mincost[1] = 0;
pre[1] = 1;
int ret = 0;
for (int cnt = 1; cnt <= n; cnt++)
{
int minval = INF, k;
for (int i = 1; i <= n; i++)
{
if (!vis[i] && minval > mincost[i])
minval = mincost[k = i];
}
// 提前結束迴圈,說明不存在最小生成樹。
if (minval == INF)
return -1;
// 標記(pre[k], k)這條邊已經使用過。
used[k][pre[k]] = used[pre[k]][k] = 1;
vis[k] = 1;
ret += minval;
for (int i = 1; i <= n; i++)
{
// 如果i在已構造的子集中,就利用維護的max_edge資訊求出max_edge[i][k]。
if (vis[i])
max_edge[i][k] = max_edge[k][i] = max(max_edge[i][pre[k]], G[pre[k]][k]);
else if (G[k][i] != INF && mincost[i] > G[k][i])
{
mincost[i] = G[k][i];
pre[i] = k;
}
}
}
return ret;
}
// 求次小生成樹的權值和
int second_MST()
{
int ret = INF;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
// 如果i, j之間有未使用過的邊,就新增(i, j),但這個時候會形成環,
// 所以要刪除環中最長的一條邊,即max_edge[i][j]。
if (i != j && G[i][j] != INF && !used[i][j])
ret = min(ret, ans1 + G[i][j] - max_edge[i][j]);
}
return ret;
}
int main()
{
//freopen("t1.txt", "r", stdin);
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &m);
init();
for (int i = 0; i < m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G[u][v] = G[v][u] = w;
}
ans1 = MST();
ans2 = second_MST();
// 如果次小生成樹等於最小生成樹,說明最小生成樹不唯一。
if (ans1 == ans2)
printf("Not Unique!\n");
else
printf("%d\n", ans1);
}
return 0;
}