这两天做了三题次小生成树包括上篇博客都是用Prim算法写的;孤陋寡闻的我还以为smst就是用prim求;
直到碰到这题,很裸的次小生成树!但和以往都不一样的是它会有重边!
这样以来用prim算法考虑的话,used[ i ][ j ]数组就无法做标记;在删除边的时候也不知道怎么删除。
于是我想了一个办法来解决它。
设一个结构体
struct node { int v[maxn];//重边的值 int cnt;//该重边的数量 int flag;//是否有重边 int flag1; }mark[maxn][maxn];
结果……XJB胡写了162行代码,代码太丑,WA了N次,也不知道哪有问题;
/* *********************************************** Author :angon ************************************************ */ #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <stack> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; #define REP(i,k,n) for(int i=k;i<n;i++) #define REPP(i,k,n) for(int i=k;i<=n;i++) #define scan(d) scanf("%d",&d) #define scann(n,m) scanf("%d%d",&n,&m) #define LL long long #define maxn 210 #define N 100000000 int mp[maxn][maxn]; int used[maxn][maxn]; int Max[maxn][maxn]; int vis[maxn],lowc[maxn],pre[maxn]; int m,n; struct node { int v[maxn]; int cnt; int flag; int flag1; }mark[maxn][maxn]; int Prim() { memset(Max,0,sizeof(Max)); memset(used,0,sizeof(used)); memset(vis,0,sizeof(vis)); for(int i=0;i<=n;i++) { pre[i]=1; lowc[i]=mp[1][i]; } lowc[1]=0; vis[1]=1; pre[1]= -1; int ans=0; for(int i=2;i<=n;i++) { int minc=N,p=-1; for(int j=1;j<=n;j++) if(!vis[j] && lowc[j]<minc) { minc=lowc[j]; p=j; } if(minc==N) return -1; ans+=minc; vis[p]=1; used[p][pre[p]]=used[pre[p]][p]=1; for(int j=1;j<=n;j++) { if(vis[j] && j!=p) Max[p][j]=Max[j][p]=max(Max[j][pre[p]],lowc[p]); if(!vis[j] && mp[p][j] < lowc[j]) { lowc[j]=mp[p][j]; pre[j]=p; } } } return ans; } int f[maxn][maxn]; int smst() { int minc=N,ans=Prim(); for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) { if(mark[i][j].flag && used[i][j]) { for(int k=0;k<mark[i][j].cnt;k++) if(k!=f[i][j]) minc=min(minc,ans+mark[i][j].v[k]-mp[i][j]); } if(mp[i][j]!=N && !used[i][j]) minc=min(minc,ans+mp[i][j]-Max[i][j]); } if(minc==N) return -1; return minc; } int main() { int t,ca=1; scan(t); while(t--) { int u,v,w; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) { mp[i][j]=mp[j][i]=N; f[i][j]=f[j][i]=0; mark[i][j].flag=mark[j][i].flag=0; mark[i][j].flag1=mark[j][i].flag1=0; mark[i][j].cnt=mark[j][i].cnt=1; } while(m--) { scanf("%d%d%d",&u,&v,&w); if(mark[u][v].flag1) { mark[u][v].flag=1; int k = mark[u][v].cnt++; // printf("k=%d\n",k); mark[u][v].v[k]=w; if(mp[u][v]>w) { f[u][v]=f[v][u]=k; mp[u][v] = mp[v][u] = w; } continue; } mp[u][v]=mp[v][u]=w; mark[u][v].v[0]=mark[v][u].v[0]=w; mark[u][v].flag1=mark[v][u].flag1=1; } // printf("f=%d\n",f); // for(int i=0;i<mark[4][5].cnt;i++) // printf("%d\n",mark[4][5].v[i]); // printf("mp=%d\n",mp[4][5]); int ans1=Prim(); printf("Case #%d : ",ca++); if(ans1==-1) { printf("No way\n"); continue; } // printf("ans1=%d\n",ans1); int ans2=smst(); if(ans2==-1) printf("No second way\n"); else printf("%d\n",ans2); } return 0; }
百度一下,原来重边用kruskal算法是极好的;又学到了。确是是这样,prim算法有点两两枚举点的感觉,与点的关系比较密切,而kruskal则是从边出发;当边有重边,再枚举点已不合适,从边出发非常符合常理。
kruskal求次小生成树和prim算法求 的核心思想应该说是一致的,都是先求出MST,再通过删除MST上的一条边,构造新的生成树;枚举所有的边,得出答案。Prim时间复杂度 O(V*V); kruskal时间复杂度 O(V*E);(自己分析的,应该没错吧0-0)
最终AC代码:
/* *********************************************** Author :angon ************************************************ */ #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <stack> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; #define REP(i,k,n) for(int i=k;i<n;i++) #define REPP(i,k,n) for(int i=k;i<=n;i++) #define scan(d) scanf("%d",&d) #define scann(n,m) scanf("%d%d",&n,&m) #define LL long long #define maxn 222 #define INF 1000000 struct Edge { int u,v,w; }edge[maxn]; bool cmp(Edge n1,Edge n2) { return n1.w<n2.w; } int p[maxn],used[maxn]; int find(int x) { if(x==p[x]) return x; return p[x]=find(p[x]); } int main() { // freopen("out.txt","w",stdout); int t,n,m,ca=1; scan(t); while(t--) { scann(n,m); REP(i,0,m) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); sort(edge,edge+m,cmp); REPP(i,0,n) p[i]=i; int ans=0,cnt=0; REP(i,0,m) { int u=edge[i].u; int v=edge[i].v; int t1=find(u); int t2=find(v); if(t1!=t2) { ans+=edge[i].w; p[t1]=t2; used[cnt++]=i; } if(cnt==n-1) break; } printf("Case #%d : ",ca++); if(cnt < n-1) { printf("No way\n"); continue; } /*求次小生成树*/ if(m==n-1) { printf("No second way\n"); continue; } int ans2=INF; //printf("cnt=%d\n",cnt); REP(i,0,cnt) { REPP(j,0,n) p[j]=j; int smst=0,ct=0; REP(j,0,m) { if(j==used[i]) //轮流删除MST中的一条边求S_MST continue; int u=edge[j].u; int v=edge[j].v; int t1=find(u); int t2=find(v); if(t1!=t2) { smst += edge[j].w; p[t1]=t2; ct++; } if(ct==n-1) break; } if(ct==n-1) ans2=min(ans2,smst); } if(ans2==INF) printf("No second way\n"); else printf("%d\n",ans2); } return 0; }