南将军率领着许多部队,它们分别驻扎在N个不同的城市里,这些城市分别编号1~N,由于交通不太便利,南将军准备修路。
现在已经知道哪些城市之间可以修路,如果修路,花费是多少。
现在,军师小工已经找到了一种修路的方案,能够使各个城市都联通起来,而且花费最少。
但是,南将军说,这个修路方案所拼成的图案很不吉利,想让小工计算一下是否存在另外一种方案花费和刚才的方案一样,现在你来帮小工写一个程序算一下吧。
-
输入
-
第一行输入一个整数T(1<T<20),表示测试数据的组数
每组测试数据的第一行是两个整数V,E,(3<V<500,10<E<200000)分别表示城市的个数和城市之间路的条数。数据保证所有的城市都有路相连。
随后的E行,每行有三个数字A B L,表示A号城市与B号城市之间修路花费为L。
输出
- 对于每组测试数据输出Yes或No(如果存在两种以上的最小花费方案则输出Yes,如果最小花费的方案只有一种,则输出No) 样例输入
-
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
样例输出
No
Yes
资料来源:
http://blog.csdn.net/tianshuai11/article/details/7065122
一,次最小生成树
定义:设T是图G的最小生成树,如果T1满足ω(T1)=min{ω(T’)| T’∈Not(T)},则T1是G的次小生成树。
解释:除了最小生成树外,另外一个生成树的权值和最小的生成树,定义为次最小生成树。
经典题目:POJ1679 The Unique MST,对于一张图,判断最小生成树是否惟一。惟一的定义是:不存在第二棵生成树,它的权值与最小生成树的权值相等。w(次最小生成树)!=w(最小生成树)
算法的思路:1,先生成一棵最小生成树,
2,然后枚举生成树以外的边,每次添加了一条边后,会产生一个环
3,依次去掉环上的除新添加的边以外的权值最大边,然后判断新的生成树与原生成树权值是否相同(不可能比原来生成树的权值要小)
总体思路:简单地说就是判断新添加的边是否与环上原有的边中权值最大的边具有相同的权值。(原因:最小生成树选取的边都是权值最小的,剩余的边>=最小生成树最大的边)
方法一:先求最小生成树,标记出构成最小生成树边,然后枚举这些边,每次删一条,然后求一次生成树,将其值保存起来。求完之后,把删除的边补回去。进行下一次删边,枚举过程中保存最小值,如果最小值跟原来的最小生成树的值相等的话,则说明,该最小生成不唯一,反之唯一。
方法二:用Prim算法求一棵最小生成树,利用Prim算法的特性,即对于每一步扩展,都保持扩展的结果是一棵树,为了方便下一步枚举边的判断,我们用一个Max数组记录最小生成树上任意两点之间的最大边权(这里存在歧义,正解为:找到i点跟j点之间所有边中最大的一条边),这一步在Prim算法中很容易做到,因为Max[i][j]=max{Max[i][k],edge[k][j]}。接下来的操作就是枚举每一条不在最小生成树中的边,对于edge[i][j],判断它是否等于Max[i][j],若相等,则说明最小生成树不惟一。
PS:需要改进的地方,我在边与点之间做了一些映射,空间开销比较大,编程复杂度也高,以后想办法写得更简洁一些。(update:对于数据量小的题,用邻接矩阵存比较方便 )
这里我用的是Krusual的方法,先生成最小树,然后在生成树中删除一条边,在构造最小生成树,比较生成树的值,得出答案。
AC代码:
#include<iostream>
#include<stdio.h>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
#define MaxV 510
#define MaxE 200005
struct Edge
{
int x,y,dis;
};
Edge edge[MaxE],edge1[MaxE]; //分别是边数组 最小生成树中的边数组
int father[MaxV],Num[MaxV],num,dex,dey; //并查集 num统计生成树边的条数 dex dey指枚举删除边的x,y坐标
void Init(int V) //并查集初始化,单个元素自成集合
{
for(int i=1;i<=V;i++)
{
father[i]=i;
Num[i]=1;
}
}
int findfather(int x) //寻找父结点,可以压缩路径。。
{
for(x;x!=father[x];x=father[x]) ;
return father[x];
}
void Union(int x,int y)
{
int t1,t2;
t1=findfather(x);
t2=findfather(y);
if(Num[t1]>Num[t2])
{
father[t2]=t1;
Num[t1]+=Num[t2];
}
else
{
father[t1]=t2;
Num[t2]+=Num[t1];
}
}
int comp(const void* p1,const void* p2)
{
return (*(Edge*)p1).dis>(*(Edge*)p2).dis;
}
int Krusual(int V,int E)
{
int sum=0;
for(int i=1;i<=E;i++)
{
if(findfather(edge[i].x)!=findfather(edge[i].y))
{
sum+=edge[i].dis;
Union(edge[i].x,edge[i].y);
edge1[++num]=edge[i];
}
}
return sum;
}
int AKrusual(int V,int E)
{
Init(V);
int sum=0;
qsort(edge+1,E,sizeof(edge[1]),comp);
int k;
for(k=1;k<=E;++k)
{
if(findfather(edge[k].x)!=findfather(edge[k].y))
{
if(edge[k].x==dex&&edge[k].y==dey)
{continue;}
if(edge[k].x==dey&&edge[k].x==dex)
{continue;}
sum+=edge[k].dis;
Union(edge[k].x,edge[k].y);
}
}
return sum;
}
bool Judge(int V) //判断图是否连通,不连通则无法构造最小生成树
{
for(int m=1;m<=V-1;++m)
if(findfather(m)!=findfather(m+1))
return false;
return true;
}
int main()
{
int test,j,V,E;
scanf("%d",&test);
while(test--)
{
scanf("%d%d",&V,&E);
num=0;
for(j=1;j<=E;++j)
{
scanf("%d%d%d",&edge[j].x,&edge[j].y,&edge[j].dis);
}
qsort(edge+1,E,sizeof(edge[1]),comp);
Init(V);
int sum=Krusual(V,E);
int M=1000,temp;
for(int q=1;q<=num;++q)
{
dex=edge1[q].x;
dey=edge1[q].y;
temp=AKrusual(V,E);
if(temp<M&&Judge(V))
M=temp;
if(M==sum) break;
}
if(M==sum) printf("Yes\n");
else printf("No\n");
}
}