1. 程式人生 > >POJ 1679 The Unique MST(演算法導論23-1次優最小生成樹)

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_edge陣列,max_edge[i][j]表示max(i, j)。按照Prim演算法,新新增進了一條邊(u, v),就可以利用維護的資訊計算出A中任意一個點k到新新增的點vmax(k, v)G[i][j]表示邊(i, j)的長度。

計算公式為:max(k, v) = max(max(k, u), G[u][v])

d. 演算法:假設當前求出的最小生成樹為T,列舉所有不屬於T的邊(u, v),向T中新增(u, v)

因為會形成環,所以要刪掉一條邊。因為我們希望得到的生成樹權值最小,所以要 刪掉環中權值最大的邊,也就是max_edge(u, v),然後就會得到新的生成樹T'。在得到

的所有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;
}