题意:判断一个图的最小生成树是否唯一
首先明确一点:最小生成树中任意加一条边必形成环,在该环中任意删除一条边,必定还是生成树,因为生成树的定义是n-1条边且无环。
步骤:1、遍历最小生成树的任意两点,连接该两点,再删除该两点所在环中的所有线段中的最长的那段,求出新树的总权值。
2、求出所有新树的权值最小的那个树就是次小生成树,判断次小生成树的权值和最小生成树的权值是否相等,
如果不相等说明最小生成树唯一。
那么如何求出任意两点所在环中所有线段中最长的那段的长度呢? 比如两点为a和b,a连在点c上,b连在d上,要想求ab所在环的线段最大值,可以求max(bc所在环的线段最大值,ac线段长)代码:Max[j][k] = Max[k][j] = max(Max[j][pre[k]], dis[k]);
验证该方程的正确性可参考以下执行过程:
完整代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 105;
int pre[maxn];
bool vis[maxn];
bool used[maxn][maxn];
int mat[maxn][maxn];
int MAX[maxn][maxn];
int dis[maxn];
int n,m;
int prim(int cur)
{
int index = cur;
memset(vis,false,sizeof(vis));
memset(used,false,sizeof(used));
memset(MAX,0,sizeof(MAX));
int sum = 0;
for (int i = 1;i <= n; i++)
{
dis[i] = mat[cur][i];
pre[i] = cur;
}
vis[cur] = true;
for (int i = 1; i < n; i++)
{
int mindis = INF;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && mindis > dis[j])
{
mindis = dis[j];
index = j;
}
}
if (mindis == INF) return -1;
sum += mindis;
vis[index] = true;
used[index][pre[index]] = true;//标记已连接的线段
used[pre[index]][index] = true;
for (int j = 1; j <= n; j++)
{
if (vis[j] && j != index)//寻找j-index所在环的最长线段
MAX[j][index] = MAX[index][j] = max(MAX[j][pre[index]],dis[index]);
if (!vis[j] && dis[j] > mat[index][j])
{
dis[j] = mat[index][j];
pre[j] = index;
}
}
}
return sum;
}
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
scanf("%d%d",&n,&m);
int u,v,w;
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++)
mat[i][j] = mat[j][i] = INF;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d",&u,&v,&w);
mat[u][v] = mat[v][u] = w;
}
int ans = prim(1);
if (ans == -1) puts("Not Unique!");//没有最小生成树的情况
else
{
int sum = INF;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!used[i][j] && mat[i][j] != INF)
sum = min(sum,ans + mat[i][j] - MAX[i][j]);//遍历任意两点
if (sum == ans) puts("Not Unique!");
else printf("%d\n",ans);
}
}
return 0;
}