Description
给出一张连通的无向图,判断其最小生成树是否唯一
Input
第一行为一整数T表示用例组数,每组用例第一行为两整数n和m表示该无向图的点数和边数,之后m行每行三个整数a,b,c表示点a与点b之间有一条权值为c的边
Output
对于每组用例,如果该图的最小生成树唯一则输出最小生成树权值和,否则输出Not Unique!
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!
Solution
求出最小生成树权值和mst和次小生成树权值和secmst,如果两者相等说明最小生成树不唯一,否则唯一,所以问题转化为如何求次小生成树,首先给出一个结论:设T为无向图G的最小生成树,那么G的次小生成树可以由T删去一条边再添加一条边构成。如果枚举这被删去和被添加的边,时间复杂度太高,一种效率比较高的做法是首先将不属于T的一条边(x,y)加到T中,那么T一定成环,那么删去环上除(x,y)之外权值最大的一条边可以得到加入(x,y)后权值最小的一棵树,如果能够很快的得到最小生成树上两点之间最长边的权值,那么枚举(x,y)就可以快速得到次小生成树,而在用Kruskal算法求最小生成树的过程中,可以在算法运行过程中求出x到y路径上的最长边,因为每次合并两个等价类的时候,分属于两等价类的点之间的最长边一定为当前新加入的边,这样我们就可以记录任意两点之间路径上的最大值,问题得到很好的解决
Code
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 1111
double dis[maxn][maxn],cost[maxn][maxn],mincost[maxn];
int x[maxn],y[maxn],z[maxn];
bool used[maxn];
int V;
double prim(double x)
{
for(int i=0;i<V;i++)
{
mincost[i]=cost[0][i]-x*dis[0][i];
used[i]=false;
}
used[0]=true;
double res=0;
for(int i=1;i<V;i++)
{
int v=-1;
double minc=INF;
for(int u=0;u<V;u++)
if(!used[u]&&mincost[u]<minc)
v=u,minc=mincost[u];
if(v==-1)break;
used[v]=true;
res+=minc;
for(int u=0;u<V;u++)
if(!used[u])
mincost[u]=min(mincost[u],cost[u][v]-x*dis[u][v]);
}
return res;
}
double get_dis(int x1,int x2,int y1,int y2)
{
return sqrt(1.0*(x1-x2)*(x1-x2)+1.0*(y1-y2)*(y1-y2));
}
int main()
{
while(~scanf("%d",&V),V)
{
for(int i=0;i<V;i++)scanf("%d%d%d",&x[i],&y[i],&z[i]);
for(int i=0;i<V;i++)
for(int j=0;j<V;j++)
cost[i][j]=abs(z[i]-z[j]),
dis[i][j]=get_dis(x[i],x[j],y[i],y[j]);
double l=0,r=100.0;
while(r-l>1e-4)
{
double mid=(l+r)/2;
if(prim(mid)>=0)l=mid;
else r=mid;
}
printf("%.3f\n",l);
}
return 0;
}