The Unique MST
题意:
· 第一行给定整数 t ,表示测试组数;
·接下来 t 组,每组第一行给定整数 n 、m,分别表示节点数和边数;
·接下来 m 行,每行 3 个整数 a、b、c,表示 a、b 间距离为 c;
· 判断最小生成树是否唯一,唯一则输出最小生成树,不唯一则输出"Not Unique!"。
数据范围:
t (1 <= t <= 20),n (1 <= n <= 100)
解题思路:
看了一下discuss,有人推荐了一个文档,上面介绍了三种思路:
①借助 Prim 算法提出的方法(O(n^2)复杂度):
“在 prim 算法中,每次把集合Q外的最靠近集合Q中的点加入集合Q(集合Q为已访问的点的集合),所以只有当集合Q外存在多个与集合Q中的点具有相同的最小边权值时,MST才有可能不唯一
。当出现这种情况时,加入下一个点时枚举所有的这些点,最后判断每次得到的形状是否一-样。这样固然可以,但时间复杂度太高了,这里提出如下方法。
当有可能存在MST不唯一时,考虑两个极端
的MST.一个是每次遇到多种选择时,都选择序号最小的点加入集合Q,另一个是每次遇到多种选择时,加入序号最大的点。如果这两个极端的MST完全一样,那么MST就是唯一的。这种方法的时空复杂度与 prim 算法的时空复杂度一样。所以时间复杂度为O(n ^ 2),空间复杂度为O(n ^ 2)。”
②借助 Kruskal 算法提出的方法(O(elog2e)~O(e^2)复杂度):
“用邻接表的数据结构存储每条边的信息,然后将每个点都看作一个单独的集合,从小到大枚举每条边,如果边的两端点不在同一个集合的话,就加入这条边,并且将这两个点所属的集合合并,否则,就不加这条边(因为同一个集合的点一定是可达的,然后加入该条边的话就一定会构成回路)。算法结束后就有n-1条不会产生回路的边了,MST 的形状也就出来了。
Kruskal 每一步都是加入最小权的边,所以当且仅当要加入的候选边中存在两条最小权值且连接的是两个相同集合的边时(此时这两条边不能同时加入,取舍导致了最终结果的不唯一
),MST才不唯一。所以在加入每条边时,都要判断后面是否存在与它权值相等且连接的是两相同集合的边。
kruskal 的时间复杂度的瓶颈在于对边排序
,后面对边扫描是O(e),而判断MST是否唯一时,排序是一样的,只是扫描时,每次加入一条边时都要向后判断是否存在一条与其权值相等且连接相同两集合的边,所以时间复杂度与边的权值相等的程度相关,最好的时候为O(elog2e), 最坏的时候为O(e^ 2), 空间复杂度与 kruskal 算法一样O(e)。”
③借助最小生成树提出的方法(O(nelog2e)的复杂度):
“次小生成树是所有生成树中权值和第二小的生成树。判断最小生成树是否唯一可以转化到判断次小生成树的权值和是否与最小生成树的权值和相等
的问题上来。如果权值和相等,那么最小生成树不唯一,否则唯一。
求次小生成树的基本思想是:先求最小生成树,然后枚举最小生成树的每一条边(总共有n-1条),删去后再求最小生成树,这样得到的所有生成树中最小的就是次小生成树。”
遗憾的是,在下还只是小白一枚,也就只能写一写比较容易想到的第三种啦  ̄□ ̄||~在此真诚地感谢wpp学长,帮我理清了思路,还附上了带详解的代码,真的很详细,所以即使是在下这样的小白也很容易就能理解啦(o^ . ^o) ~以下是我自己的AC代码。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define zero 1e-7
typedef long long ll;
const int N=5100;//之前N=110,edge[N*N/2],结果怎么改都RE,原来是数组开小了,果然我还是太嫩o(╥﹏╥)o
int par[N], num[N];//par数组记录每个点的父节点,num数组记录每个节点连的边数
bool flag[N];//标记最小生成树中加入的边
int m, n;
struct node {
int from, to;
int w;
}edge[N];
bool cmp(node x, node y) {
return x.w<y.w;
}
void init() {
for(int i=1; i<=n; i++)
par[i]=i;//初始化每个节点的父节点为自己本身
return ;
}
int find(int a) {
if(a==par[a]) return a;
else return par[a]=find(par[a]);
}
int kruskal() {//求最小生成树
init();// !!!初始化
sort(edge, edge+m, cmp);
memset(flag, false, sizeof(flag));
int ans=0, a, b, cnt=0;
for(int i=0; i<m; i++) {
if(cnt==n-1) break;
a=find(edge[i].from);
b=find(edge[i].to);
if(a!=b) {
par[b]=a;
ans+=edge[i].w;
flag[i]=true;
cnt++;
}
}
return ans;
}
int kruskal2(int ex) {//求次小生成树,ex表示要删除的边
init();// !!!初始化
int ans=0, a, b, cnt=0;
for(int i=0; i<m; i++) {
if(cnt==n-1) break;
a=find(edge[i].from);
b=find(edge[i].to);
if(a!=b && i!=ex) {
ans+=edge[i].w;
par[b]=a;
cnt++;
}
}
if(cnt!=n-1) ans=INF;
return ans;
}
int main() {
int t, ans, ans2;
scanf("%d", &t);
while(t--) {
memset(num, 0, sizeof(num));
scanf("%d %d", &n, &m);
for(int i=0; i<m; i++) {
scanf("%d %d %d", &edge[i].from, &edge[i].to, &edge[i].w);
num[edge[i].from]++;
num[edge[i].to]++;
}
ans=kruskal();
ans2=INF;
for(int i=0; i<m; i++) {
//敲黑板!如果一个点上只连了一条边,那这条边一定包含在所有生成树里面,否则这个点就不能与其他点连通
if(num[edge[i].from]>1 && num[edge[i].to]>1 && flag[i]) {
ans2=min(ans2, kruskal2(i));//求所有次小生成树里面最小的那个,最后与最小生成树比较
}
}
if(ans==ans2) printf("Not Unique!\n");
else printf("%d\n", ans);
}
return 0;
}