这两天做了三题次小生成树包括上篇博客都是用Prim算法写的;孤陋寡闻的我还以为smst就是用prim求;
直到碰到这题,很裸的次小生成树!但和以往都不一样的是它会有重边!
这样以来用prim算法考虑的话,used[ i ][ j ]数组就无法做标记;在删除边的时候也不知道怎么删除。
于是我想了一个办法来解决它。
设一个结构体
struct node
{
int v[maxn];//重边的值
int cnt;//该重边的数量
int flag;//是否有重边
int flag1;
}mark[maxn][maxn];
特意用来处理重边问题;mp[i][j]只要保存的是重边中最小的那条就不影响MST的求值;只要在算SMST的时候把重边考虑进去就行了。
结果……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;
}