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;
}






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值