原题链接:http://poj.org/problem?id=1679
The Unique MST
Given a connected undirected graph, tell if its minimum spanning tree is unique.
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:
- V’ = V.
- 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
The first line contains a single integer t (1 <= t <= 20), the number of test cases. Each case represents a graph. It begins with a line containing two integers n and m (1 <= n <= 100), the number of nodes and edges. Each of the following m lines contains a triple (xi, yi, wi), indicating that xi and yi are connected by an edge with weight = wi. For any two nodes, there is at most one edge connecting them.
Output
The first line contains a single integer t (1 <= t <= 20), the number of test cases. Each case represents a graph. It begins with a line containing two integers n and m (1 <= n <= 100), the number of nodes and edges. Each of the following m lines contains a triple (xi, yi, wi), indicating that xi and yi are connected by an edge with weight = wi. For any two nodes, there is at most one edge connecting them.
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!
题解:题目意思是问我们MST是否唯一,唯一输出MST的值,否则输出Not Unique!
通过给出第二个例子(如下图):明显不唯一
在网上看了好多贴总结了两句话:
1.枚举每条边不是最小生成树上面的边,并把这条边放在最小生产数上面,一定会形成环,那么在这个环路上去取一条最长的路(除了刚刚加入的边),就构成了次小生成树。
2.在1.的基础上,取出的边等于去掉的边,MST不唯一; 取出的边大于去掉的边,则可能是次小生成树,枚举所有不在MST的边即可得到次小生产数。
这里不讨论次小生成树,这道题是讨论MST唯不唯一,所以只是借用了次小生生成树的一个推导。
很多人可能和我一样都是不明白别人代码的maxd,所以我在这里从头到尾分析一下maxd怎么来的和maxd用来干嘛。
很多博客上都简单的说了一句:maxd保存i到j的最大边,然后没了,没了。。。。像我这种比较菜的人看了一天,才明白是上面意思。
首先分析怎么来的,先贴一段(prim算法,不懂戳这)代码:
int prim(int s){
memset(vis,false,sizeof(vis));
memset(maxd,0,sizeof(maxd));
for(int i=1;i<=n;i++){
dis[i]=mp[s][i];//更新到其他点的距离
pre[i]=s;//初始化父节点为s
}
dis[s]=0;
vis[s]=true;
int ans=0;
for(int i=1;i<=n;i++){
int minid=-1;
int min=inf;
for(int j=1;j<=n;j++){
if(!vis[j]&&dis[j]<min){
minid=j;//保存最小点的下标
min=dis[j];//保存最小边的值
}
}
if(minid==-1)//没找到表示所有点都找完了
return ans;
int fa=pre[minid];//存储父节点
vis[minid]=true;
ans+=min;//更新MST
connect[minid][fa]=connect[fa][minid]=false;//表示边在MST上了,不能再用
maxd[fa][minid]=maxd[minid][fa]=min;//找到了一条边,直接更新
for(int j=1;j<=n;j++){
if(j!=minid){
maxd[j][minid]=maxd[minid][j]=max(maxd[j][fa],maxd[fa][minid]);//间接更新 ,更新任意点到minid的最长边
}
}
//输出打表
/*for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
cout<<maxd[j][k]<<" ";
}
cout<<endl;
}
cout<<endl;
*/
for(int j=1;j<=n;j++){
if(!vis[j]&&mp[minid][j]<dis[j]){
dis[j]=mp[minid][j];
pre[j]=minid;
}
}
}
return ans;
}
上面的一段代码是网上的一个模板,具体流程根prim算法差不多,只是多了connect数组和maxd数组connect数组不比多说,就是表示边是否用过。
那么定位maxd的代码:
maxd[fa][minid]=maxd[minid][fa]=min;//直接更新
和
for(int j=1;j<=n;j++){
if(j!=minid){
maxd[j][minid]=maxd[minid][j]=max(maxd[j][fa],maxd[fa][minid]);//间接更新
}//if
}//for
当我们找到一条min边的时候,我们需要直接更新这个步骤,然后一个for循环来个间接更新,究竟有什么作用呢?
我们从头说起:
在推导1中,如果加入一条边生成环,我们去一条MST最长的边,那么我们去哪里取呢,当然取maxd中取,因为前面说的maxd表示任意两点在MST总的最长边,
那么maxd怎么来呢?
我们要去掉的是MST上面的边,所以每次找到一条边则把它加入maxd中,也就是直接更新,
但是我们要取得是环上最长的。
怎么保证环上面最长的呢?
不说特殊的两个点成环,说三个点成环开始,在构建MST时,三个点肯定出现(图上的情况),如果再加一条边,显然成环,刚刚说了要去掉MST上面的最长的边,且存在maxd中,我们假设1为j,2为pre(为父节点),3为minid(为当前节点),那么1…3构成的环,最长的边肯定是1->2或2->3这两条边的其中一条,换成代码的话如:maxd[1][3]=max(maxd[1][2],maxd[2][3])(maxd[j][minid]=max(maxd[j][pre],maxd[pre][minid]),我们这样我们就能得到j到minid的最长边。
再加一个点的时候(如上图),任意两点都有可能构成环,所以我们要求的当前节点到其他节点构成环的最长边。
那么上面我们可以求得1->2为4,是1…3构成环中最长的。那么1…4构成环最长的边显然也是这条,上面我们刚好处理过了,并且存储在maxd[1][3]中,存储着4。那么我们在处理一次maxd[1][4]=max(maxd[1][3],maxd[3][4])的话,就可以得到1…4构成环最长的边,如果我们转换成代码:maxd[j][minid]=max(maxd[j][pre],maxd[pre][minid]),
正是间接操作的部分!!!出来了,终须明白了!!!
五个点,六个点,以此类推。
当然机器无法像人一样直接辨别j,pre是多少,我们可以枚举,把1到n个点,出minid本身之外的点pre的maxd都枚举一遍,和新加入的maxd[pre][minid]比较,这样就可以得到1到n的点到minid构成环的最大值存储在maxd[][minid]列中,那么当prim算法发每加入一个点,就把minid列都更新完,这样当prim算法完成之后,任何两个点构成的最长边就完成了。
for(int j=1;j<=n;j++){
if(j!=minid){
maxd[j][minid]=maxd[minid][j]=max(maxd[j][fa],maxd[fa][minid]);//间接更新
}
}
如果还不明白的话,你可以打一下maxd表出来,看一下任意两点之间构成的环是不是maxd的值
//输出
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
cout<<maxd[j][k]<<" ";
}
cout<<endl;
}
最后在主函数有个判断:
bool over=false;
for(int i=1;!over&&i<=n;i++){
for(int j=1;j<=n;j++){
if(connect[i][j]==false||mp[i][j]==inf)
continue;
if(mp[i][j]==maxd[i][j]){
over=1;
break;
}
}
}
在推导2.中,取出的边等于去掉的边,则MST不唯一,为什么呢,既然加入的边和要去掉的边的值相等,那么就是加入的边可以替换去掉的边,MST就不唯一了 如果ij这条边在MST上(connect[i][j]==false)或者ij这条边无限大(mp[i][j]==inf),就不比较了
如果ij这条边不在MST上,且不是无限大,又可以替换MST上面的边(mp[i][j]==maxd[i][j]),那么MST就不唯一了。
AC代码:
#include<iostream>
#include<string.h>
#include<cmath>
#define maxn 110
#define inf 0x3f3f3f3f
using namespace std;
int mp[maxn][maxn];//存储地图
int maxd[maxn][maxn];//存储ij构成环最长的边
bool connect[maxn][maxn];//表示是否在MST上
bool vis[maxn];//表示节点是否访问过
int dis[maxn];//存储到节点的距离
int pre[maxn];//存储父节点
int n,m;
int prim(int s){
memset(vis,false,sizeof(vis));
memset(maxd,0,sizeof(maxd));
for(int i=1;i<=n;i++){
dis[i]=mp[s][i];//更新到其他点的距离
pre[i]=s;//初始化父节点为s
}
dis[s]=0;
vis[s]=true;
int ans=0;
for(int i=1;i<=n;i++){
int minid=-1;
int min=inf;
for(int j=1;j<=n;j++){
if(!vis[j]&&dis[j]<min){
minid=j;//保存最小点的下标
min=dis[j];//保存最小边的值
}
}
if(minid==-1)//没找到表示所有点都找完了
return ans;
int fa=pre[minid];//存储父节点
vis[minid]=true;
ans+=min;//更新MST
connect[minid][fa]=connect[fa][minid]=false;//表示边在MST上了,不能再用
maxd[fa][minid]=maxd[minid][fa]=min;//找到了一条边,直接更新
for(int j=1;j<=n;j++){
if(j!=minid){
maxd[j][minid]=maxd[minid][j]=max(maxd[j][fa],maxd[fa][minid]);//间接更新 ,更新任意点到minid的最长边
}
}
//输出打表
/*for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
cout<<maxd[j][k]<<" ";
}
cout<<endl;
}
cout<<endl;
*/
for(int j=1;j<=n;j++){
if(!vis[j]&&mp[minid][j]<dis[j]){
dis[j]=mp[minid][j];
pre[j]=minid;
}
}
}
return ans;
}
int main(){
int t;
cin>>t;
while(t--){
cin>>n>>m;
memset(mp,inf,sizeof(mp));
memset(connect,false,sizeof(connect));
int x,y,w;
for(int i=0;i<m;i++){
cin>>x>>y>>w;
mp[x][y]=mp[y][x]=w;
connect[x][y]=connect[y][x]=true;
}
int ans=prim(1);
bool over=false;
for(int i=1;!over&&i<=n;i++){
for(int j=1;j<=n;j++){
if(connect[i][j]==false||mp[i][j]==inf)//看边是否在MST上,判断边的值是否为inf
continue;
if(mp[i][j]==maxd[i][j]){//如果能替换,MST不唯一
over=1;
break;
}
}
}
if(over)
cout<<"Not Unique!"<<endl;
else
cout<<ans<<endl;
}
return 0;
}