(POJ-1679)次小生成树模板

原题链接: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:

  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
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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值