POJ - 1679 The Unique MST (次小生成树)
题意:就是判断图的最小生成树是否唯一,即求其最小生成树(MST)和次小生成树。
虽然是一道模板题,更重要的是理解求次小生成树的过程。求次小生成树建立在Prim算法的基础上。可以确定的是,次小生成树肯定是由最小生成树删去一条边再加上一条边得到。那么我们应该删去哪条边再加上哪条边呢?假设两点u,v之间有一条边且这条边不在MST中,那么可以尝试加上这条边。但是加上这条边以后会出现环,则一定要去掉回路上的一条边,这条边应该选择回路上权值最大的那条边(毕竟权值要求尽量小)。尝试对每对点对进行上述操作,那么最小的那个结果就是次小生成树。
算法中用一个二维数组path[u][v]维护已经确定的最小生成树上的每对结点之间的路径上,权值最大的那条边,而这个维护的过程就是在Prim算法中增加的部分。
当求出最小生成树以后,我们也得到了完整的path数组。然后枚举任意两个点对u和v,求出min(MST-path[u][v]+G[u][v]),结果就是次小生成树的值。
邻接矩阵实现:
#include<iostream> #include<stdio.h> #include<cstring> #include<algorithm> using namespace std; const int maxn =1e2+5; const int INF = 0x3f3f3f3f; int G[maxn][maxn],dist[maxn]; int path[maxn][maxn]; //记录生成树上每对结点得最大边权 bool used[maxn][maxn]; int pre[maxn]; bool vis[maxn]; struct Edge{ int to,val; }; void init(int N) { for(int i=0;i<=N;++i){ for(int j=i;j<=N;++j){ G[i][j]=G[j][i]=INF; } } } void AddEdge(int u,int v,int val) { G[v][u]=G[u][v]=val; } int prim(int N) { int Mst=0; memset(used,0,sizeof(used)); memset(path,0,sizeof(path)); memset(vis,0,sizeof(vis)); vis[1]=true; //从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; vis[u]=true; Mst+=G[pre[u]][u]; for(int j=1;j<=N;++j){ if(vis[j] && j!=u){ //若j已经被访问且不是u本身,更新点对u和j的path path[u][j]=path[j][u]=max(path[j][pre[u]],dist[u]); } else if(!vis[j]){ //若j没被访问,更新dist if(dist[j]>G[u][j]){ dist[j]=G[u][j]; pre[j]=u; } } } } return Mst; } int main() { int T,N,M,u,v,tmp; scanf("%d",&T); while(T--){ scanf("%d%d",&N,&M); init(N); for(int i=0;i<M;++i){ scanf("%d%d%d",&u,&v,&tmp); AddEdge(u,v,tmp); AddEdge(v,u,tmp); } int Mst=prim(N); int res=INF; for(int i=1; i<=N; ++i){ for(int j=1; j<=N; ++j){ if(i!=j && !used[i][j]){ res=min(res,Mst+G[i][j]-path[i][j]); //检查每对结点 } } } if(res==Mst)printf("Not Unique!\n"); else printf("%d\n",Mst); } return 0; }
为了更好的明天