只需要讲解算法导论的题即可。
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到新添加的点v的max(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;
}
因篇幅问题不能全部显示,请点此查看更多更全内容