这是一道求次小生成树的题目。
首先,介绍一下什么是次小生成树。设T是无向连通图G的最小生成树,对于另一棵生成树T1(T1不等于T),如果不存在T2(T2不等于T),满足T2的权值之和小于T1的条件,那么T1称为次小生成树。从权值之和的角度看,次小生成树的权值可能大于最小生成树,也可能等于最小生成树。顺便提一下,如果无向连通图G的每条边权值均不相等,那么最小生成树唯一,也就是说,最小生成树的权值之和唯一。(证明,请参考《ACM—ICPC程序设计系列 图论及应用》(哈尔滨工业大学出版社)P39)
了解了这些,就可以明白为什么是求次小生成树了,求出次小生成树的权值,与最小生成树的权值相比较,如果不相等则唯一,否则就不唯一。
其次介绍一下求次小生成树的思路。
最朴素的想法就是在图G中,删除一条最小生成树的边,然后再求最小生成树,如此循环处理,此时对于原图来说,次小生成树一定存在其中,并且是其中权值之和最小的那棵。
还有一种思路,在求最小生成树时,先记录下任意两点间的最大权值的值,然后枚举属于图G但不属于最小生成树T的边,此时枚举的边正好与最小生成树中的部分边构成一个环,去除环中除了枚举的边之外权值最大的边,如此循环处理,次小生成树一定存在其中,并且是其中权值之和最小的那棵。那么,如何求树两点之间的最大权值呢?首先需要记录每个集合中的顶点标号,每次添加到最小生成树上的边的权值,都是这两个集合间的点之间的最大权值。在我的代码里,记录每个集合中点的方法,借用了链式前向星中存边的方法。(更具体的,请参考《ACM—ICPC程序设计系列 图论及应用》(哈尔滨工业大学出版社)P47)
最后,说一些题外话。从交题之后的时间上来看,两种思路耗时一样多,不知为何。此外,写代码时忘记写并查集中的合并操作,导致提交N次都TLE,让我郁闷了好几天,直到今天,才被我偶然发现。出现这个问题,真是令人伤脑筋。此外,通过复制粘贴的方式写那些差不多的代码,也经常由于一些小的错误改半天,所以,下次写代码越是心急,越不能复制粘贴代码,因为急的时候根本不愿仔细看复制来的代码中需要修改的地方,很多地方可能没改到,那样更容易错,有时错误更隐蔽。
代码(C++):
方法1:
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define MAX 103
using namespace std;
struct edge{
int u;
int v;
int w;
};
edge array[MAX*MAX];
int fa[MAX],n,m;
bool tag[MAX*MAX];
bool cmp(edge a,edge b)
{
if(a.w!=b.w) return a.w<b.w;
if(a.u!=b.u) return a.u<b.u;
return a.v<b.v;
}
int find(int a)
{
if(fa[a]!=a) fa[a]=find(fa[a]);
return fa[a];
}
int kruskal(int p) //amount记录生成树的边的条数 ,mstw表示去除标号为m的最小生成树的权值之和
{
int i,amount=0,mstw=0,fu,fv;
for(i=0;i<m;i++)
{
if(i==p) continue;
fu=find(array[i].u);
fv=find(array[i].v);
if(fu==fv) continue;
fa[fu]=fv;
mstw+=array[i].w;
amount++;
if(amount==n-1) break;
}
if(i==m) return -1; // 如果不存在mst,则返回-1
else return mstw;
}
int kruskal() //amount记录生成树的边的条数 ,mstw表示去除标号为m的最小生成树的权值之和
{
int i,amount=0,mstw=0,fu,fv;
for(i=0;i<m;i++)
{
fu=find(array[i].u);
fv=find(array[i].v);
if(fu==fv) continue;
fa[fu]=fv;
mstw+=array[i].w;
tag[i]=true;
amount++;
if(amount==n-1) break;
}
return mstw;
}
int main(int argc, char *argv[])
{
int i,j,k,t,u,v,w,ans,sec_mstw; //sec_mstw表示次小生成树的权值之和
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
for(i=0;i<m;i++)
{
scanf("%d %d %d",&u,&v,&w);
array[i].u=u;
array[i].v=v;
array[i].w=w;
}
sort(array,array+m,cmp);
for(i=0;i<=n;i++) fa[i]=i;
memset(tag,false,sizeof(tag));
ans=kruskal();
for(i=0;i<m;i++)
{
if(tag[i])
{
for(j=0;j<=n;j++) fa[j]=j;
sec_mstw=kruskal(i);
if(sec_mstw==ans) break;
}
}
if(i<m) printf("Not Unique!\n");
else printf("%d\n",ans);
}
system("PAUSE");
return EXIT_SUCCESS;
}
方法2:
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define MAX 103
#define INF 99999999
using namespace std;
struct edge{
int u;
int v;
int w;
};
edge array[MAX*MAX];
int fa[MAX],head[MAX],next[MAX],end[MAX],length[MAX][MAX];
bool tag[MAX*MAX];
bool cmp(edge a,edge b)
{
if(a.w!=b.w) return a.w<b.w;
if(a.u!=b.u) return a.u<b.u;
return a.v<b.v;
}
int find(int a)
{
if(fa[a]!=a) fa[a]=find(fa[a]);
return fa[a];
}
int main(int argc, char *argv[])
{
int n,m,i,j,k,t,u,v,w,fu,fv,amount,mstw,sec_mstw; //amount记录生成树的边的条数 ,mstw表示最小生成树的权值之和,sec_mstw表示次小生成树的权值之和
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
for(i=0;i<m;i++)
{
scanf("%d %d %d",&u,&v,&w);
array[i].u=u;
array[i].v=v;
array[i].w=w;
}
sort(array,array+m,cmp);
for(i=0;i<=n;i++) fa[i]=i;
amount=mstw=0;
for(i=0;i<=n;i++) head[i]=end[i]=i;
memset(next,-1,sizeof(next));
memset(tag,false,sizeof(tag));
sec_mstw=INF;
for(i=0;i<m;i++)
{
fu=find(array[i].u);
fv=find(array[i].v);
if(fu==fv) continue;
for(j=head[array[i].u];j!=-1;j=next[j])
{
for(k=head[array[i].v];k!=-1;k=next[k])
{
length[j][k]=length[k][j]=array[i].w;
}
}
next[end[array[i].u]]=head[array[i].v];
end[array[i].u]=end[array[i].v];
head[array[i].v]=head[array[i].u];
fa[fu]=fv;
tag[i]=true;
mstw+=array[i].w;
amount++;
if(amount==n-1) break;
}
for(i=0;i<m;i++)
{
if(!tag[i])
{
sec_mstw=min(sec_mstw,mstw+array[i].w-length[array[i].u][array[i].v]);
if(sec_mstw==mstw) break;
}
}
if(sec_mstw!=mstw) printf("%d\n",mstw);
else printf("Not Unique!\n");
}
system("PAUSE");
return EXIT_SUCCESS;
}
题目 http://poj.org/problem?id=1679:
Time Limit: 1000MS | Memory Limit: 10000K | |
Description
Definition 1 (Spanning Tree): Consider a connected, undirected graph G = (V, E). A spanning tree of G is a subgraph of G, say T = (V', E'), with the following properties:
1. V' = V.
2. T is connected and acyclic.
Definition 2 (Minimum Spanning Tree): Consider an edge-weighted, connected, undirected graph G = (V, E). The minimum spanning tree T = (V, E') of G is the spanning tree that has the smallest total cost. The total cost of T means the sum of the weights on all the edges in E'.
Input
Output
Sample Input
2 3 3 1 2 1 2 3 2 3 1 3 4 4 1 2 2 2 3 2 3 4 2 4 1 2
Sample Output
3 Not Unique!