题目描述(传送门)
给定连接的无向图,告诉它的最小生成树是否唯一。
定义1(生成树):考虑连通的无向图G =(V,E)。G的生成树是G的子图,比如T =(V',E'),具有以下属性:
1.V'= V.
2.T是连通的和非循环的。
定义2(最小生成树):考虑边加权,连通,无向图G =(V,E)。G的最小生成树T =(V,E')是总成本最小的生成树。T的总成本是指E'中所有边缘的权重之和。
输入
第一行包含单个整数t(1 <= t <= 20),即测试用例的数量。每个案例代表一个图表。它以包含两个整数n和m(1 <= n <= 100),节点数和边数的行开始。以下m行中的每一行包含三元组(xi,yi,wi),表示xi和yi通过权重= wi的边连接。对于任何两个节点,最多只有一个边连接它们。
产量
对于每个输入,如果MST是唯一的,则打印它的总成本,否则打印字符串'Not Unique!'。
样本输入
2
3 3
1 2 1
2 3 2
3 1 3
4 4
1 2 2
2 3 2
3 4 2
4 1 2
2 3 2
3 4 2
4 1 2
样本输出
3
Not Unique!
分析
拿到这道题的第一反应是,这TM什么鬼,鬼知道你是不是唯一的啊!!!
但静下心仔细想想,就能想到拿最小生成树MST和次小生成树MST比较一下就知道了,如果相等,说明其不唯一。反之,唯一。
那对于此题该如何做呢,很快就会想到Kruskal算法,先把边从小到大排序,再一条一条边地插入再用并查集构成最小生成树,
那又该怎样求次小生成树呢 ?
只需要将最小生成树的边破坏一条,再求一次最小生成树就可以了。
如果不懂结合代码理解理解
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int t , n , m , f[101] ;// f 用来记录父亲节点
struct node{
int f , t , w ;
// f 表示起点, t 表示终点, w 表示权值
};
node e[10001] ;//图的边
int add[101] ;//记录最小生成树的边
bool cmp( node x , node y )
{
return x.w < y.w;
}
int findf(int x)
{
while( x != f[x]) x = f[x] = f[f[x]];//查找父亲节点
return x;
}
bool Un(int x , int y)
{
int fx , fy ;
fx = findf(x);
fy = findf(y);
if( fy != fx )//如果父亲节点不相等,则连接父亲节点
{
f[fy] = fx ;
return 1 ;//表示连接了两条边
}
return 0 ;
}
int K( int x )//表示破坏的边
{
int MST = 0 , cnt = 0 ;
for(int i = 1 ; i <= n ; ++ i )//重置父亲节点
f[i] = i ;
for(int i = 1 ; i <= m ; ++ i )//枚举边
{
if(i == x) continue;
if( Un( e[i].f , e[i].t ) )//如果两点连接
{
MST += e[i].w ;
cnt ++ ;
if(x==0) add[cnt] = i;//记录最小生成树的边
}
if( cnt == n - 1 )
return MST;
}
return -1 ;
}
int main()
{
scanf("%d", &t );
for(int k = 1 ; k <= t ; ++ k )
{
memset( f , 0 , sizeof(f) );
scanf("%d%d", &n , &m );
if( n == 1 && m == 0 )
{
cout<< 0 <<endl ;
continue;
}
for(int i = 1 ; i <= m ; ++ i )
{
scanf("%d%d%d", &e[i].f , &e[i].t , &e[i].w );
}
sort( e + 1 , e + m + 1 , cmp );//对边进行排序
int MST = K(0);//求出最小生成树的MST
bool flag = 0;
for(int i = 1 ; i < n ; ++ i )
if(!flag && K(add[i])==MST) {
puts("Not Unique!");
flag = 1;
}
if(!flag) printf("%d\n",MST);
}
}
对于图论,本人也是初学者,写这篇博客时也颇有感触