7-24 最小生成树的唯一性 (35分)
给定一个带权无向图,如果是连通图,则至少存在一棵最小生成树,有时最小生成树并不唯一。本题就要求你计算最小生成树的总权重,并且判断其是否唯一。
输入格式:
首先第一行给出两个整数:无向图中顶点数 N(≤500)和边数 M。随后 M 行,每行给出一条边的两个端点和权重,格式为“顶点1 顶点2 权重”,其中顶点从 1 到N 编号,权重为正整数。题目保证最小生成树的总权重不会超过 230。
输出格式:
如果存在最小生成树,首先在第一行输出其总权重,第二行输出“Yes”,如果此树唯一,否则输出“No”。如果树不存在,则首先在第一行输出“No MST”,第二行输出图的连通集个数。
输入样例 1:
5 7
1 2 6
5 1 1
2 3 4
3 4 3
4 1 7
2 4 2
4 5 5
输出样例 1:
11
Yes
输入样例 2:
4 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3
输出样例 2:
4
No
输入样例 3:
5 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3
输出样例 3:
No MST
2
这个题不单单是让求最小生成树的总路径长度,而且还让判断最小生成树的唯一性。因为对并查集印象比较深刻,我首先想到了克鲁斯卡尔算法,可在判断最小生成树的唯一性上老是出错,最后不得已,剽窃了他人的劳动成果,将自己写的代码按照别人的想法做了修改,结果就过了,不过心里还是很不爽。在思考过程中,我甚至想到了“去边法重新构造法”,这种方法十有八九会超时,就把这个方案否决了。用Prim算法可能更直接,不过我和Prim算法还不是很熟,也把这个方法舍弃了。这个题的Kruskal解法见如下代码:
#include <iostream>
#include <algorithm>
using namespace std;
struct Book{
int u,v,w;
}book[125010];
int V[520],ans = 0;
int p[520],r[520];
int G[520][520]={0};
bool cmp(Book a,Book b){
if(a.w!=b.w)
return a.w<b.w;
return false;
}
void Init(int n)
{
for(int i=0;i<=n;i++)
{
r[i] = 0;
p[i] = i;
}
}
int Find(int x){
return x==p[x]?x:p[x]=Find(p[x]);
}
void Union(int x,int y)
{
x = Find(x);y=Find(y);
if(x==y)return ;
if(r[x]>r[y])
p[y] = x;
else if(r[y]>r[x])
p[x] = y;
else{
p[x] = y;
r[y]++;
}
}
int main()
{
//freopen("in","r",stdin);
int n,m;scanf("%d%d",&n,&m);Init(n);
for(int i=0;i<m;i++)
scanf("%d%d%d",&book[i].u,&book[i].v,&book[i].w);
sort(book,book+m,cmp);
int cnt = 0,all = 0,flag = 0;
for(int i=0;i<m;i++){
int pu,pv;
int u = book[i].u,v=book[i].v;
u=Find(u),v=Find(v);
if(u!=v){
for(int j=i+1;j<m;j++){
if(book[j].w==book[i].w){
pu=Find(book[j].u);pv=Find(book[j].v);
if((pu==u&&pv==v)||(pv==u&&pu==v))
flag=1;
}else{
break;
}
}
Union(u,v);
cnt++;
all+=book[i].w;
}
}
if(cnt==n-1)
{
printf("%d\n",all);
if(flag)
printf("No\n");
else
printf("Yes\n");
}else{
printf("No MST\n");
cnt = 0;
for(int i=1;i<=n;i++)
if(p[i]==i)cnt++;
printf("%d\n",cnt);
}
return 0;
}