题目大意:给出n个点,m条边的图。判断最小生成树是否唯一。
解题思路:就是一个次小生成树的问题,求次小生成树与最小生成树的权值是否一致即可。做法有两种:一是Prim进行判断,方法:求一次最小生成树,将生成树的边标记,并记录MST值。然后枚举删边。枚举除生成树外的其它边,更新一个最小的生成树的权值。最后比较这两个权值时候相等。相等则说明不唯一,否则唯一,输出权值。二是Kruskal算法,做法是:求一次最小生成树,标记生成树的边,然后枚举这些边,除枚举边外其它边求一次最小生成树,看权值是否相等,相等则说明不唯一。两种解法详见code。
题目来源:http://poj.org/problem?id=1679
Prim code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100+10;
const int inf = 99999999;
int t,n,m,x,y,w;
int g[MAXN][MAXN],dist[MAXN],vis[MAXN],path[MAXN][MAXN],pre[MAXN];
bool used[MAXN][MAXN];
int prim(){
int ans=0;
memset(vis,0,sizeof(vis));
memset(path,0,sizeof(path));
memset(used,0,sizeof(used));
vis[1]=1;
for(int i=1;i<=n;++i){
dist[i]=g[1][i];
pre[i]=1;
}
for(int i=1;i<n;++i){
int u=-1;
for(int j=1;j<=n;++j)
if(!vis[j] && (u==-1 || dist[j]<dist[u])) u=j;
used[u][pre[u]]=used[pre[u]][u]=true;
ans+=g[pre[u]][u];
vis[u]=1;
for(int k=1;k<=n;++k){
if(vis[k] && k!=u)
path[u][k]=path[k][u]=max(path[k][pre[u]],dist[u]);
if(!vis[k] && dist[k]>g[u][k]){
dist[k]=g[u][k];
pre[k]=u;
}
}
}
return ans;
}
int main(){
//freopen("input.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
if(i==j) g[i][j]=0;
else g[i][j]=inf;
}
for(int i=0;i<m;++i){
scanf("%d%d%d",&x,&y,&w);
g[x][y]=w;
}
int mst=prim();
int ans=inf;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(i!=j && !used[i][j])
ans=min(ans,mst+g[i][j]-path[i][j]); //次小生成树
if(ans==mst) printf("Not Unique!\n");
else printf("%d\n",mst);
}
return 0;
}
Kruskal code:终于还是A了,找了好几个小时都没有找到错误的,最后换交C++,一下就A了,还是很纠结这两个怎么选择呀!
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100+10;
int t,n,m;
int p[MAXN];
struct node{
int x,y,w;
int flag;
}g[MAXN*MAXN];
int cmp(node a,node b){return a.w<b.w;}
int find(int x){return p[x]==x ? x : p[x]=find(p[x]);}
int Kruskal(int tmp){
int ans=0,cnt=1;
for(int i=0;i<=n;++i) p[i]=i;
sort(g,g+m,cmp);
for(int i=0;i<m;++i){
if(i==tmp) continue; //如果是生成树的边则跳过,这里求最小生成树是传m进来,因此巧妙避开了计算
int x=find(g[i].x);
int y=find(g[i].y);
if(x!=y){
if(m==tmp) g[i].flag=1; //如果是求最小生成树,则需要标记生成树的边
p[x]=y;
ans+=g[i].w;
cnt++;
}
}
if(cnt!=n) return -1; //看是否存在孤立的点
else return ans;
}
int main(){
//freopen("input.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=0;i<m;++i){
scanf("%d%d%d",&g[i].x,&g[i].y,&g[i].w);
g[i].flag=0; //初始化都标记为0
}
int mst=Kruskal(m);
int flag=0;
for(int i=0;i<m;++i){
if(g[i].flag==0) continue; //如果不是生成树的边则跳过
int ans=Kruskal(i);
if(mst==ans){ //求次小生成树是否和最小生成树相等
flag=1;
break;
}
}
if(flag) printf("Not Unique!\n");
else printf("%d\n",mst);
}
return 0;
}