POJ-1679 The Unique MST

题目链接:http://poj.org/problem?id=1679


题目大意:

给你一个有权值的无相图,判断最小生成树是否唯一。


解题思路:

网上的资料很多,但是没有关于次小生成树的概念。从名字上看,次小生成树就是在最小生成树的基础上,其中某些边大于最小生成树,但是大于其他的生成树,类似于第二小生成树的意思。。。。

这道题就是用到了这种思想。

首先,我们可以通过prim算法得到这个图的最小生成树MST。之后,我们通过某些操作就可以判断MST是否唯一。

整体思想就是,先得到MST。然后,我们可以在MST外选择一条边加入MST,这时,必然会形成一个环,那么我们就在MST上删去一条边(不能是加入的边),这样,我们就得到了另一个MST。如果这个MST的权值之和和原来的MST权值相同,就有多种构图形成MST。输出YES。


算法实现流程:

1.首先使用prim算法为基础,在此基础上加入数组max1[i][j],用于记录i到j路径上的权值最大的边。

2.加入stack[i]数组,用于记录加入到MST中的顶点

3.加入pre[i]数组,用于记录加入MST的顶点的相关联边的直接前驱。

4.在找到temp和k后,需要进行一次循环,用于更新新加入点到MST各点路径最大值(作用在于,如果我要在这个顶点加边形成环,我需要去掉这个环上权值最大的边)至于为什么要用

max(max1[stack[j]][pre[k]], temp)

比较的是pre[k],因为加入的k点如果要跟MST其他点连接,必须通过它的直接前驱,而它的前驱一定是MST中已经确定的到各个顶点都是最大值的记忆化状态,所以我们要比较的是pre[k],而后面之所以是temp因为,temp是MST和非MST两个集合相连的最小权值的边,但是你要加入MST,不能保证他是MST中权值最小的(甚至是最大的)所以加入时需要用temp和max1[stack[j][pre[k]]比较,来得到max1[i][j](i到j路径上权值最大的边)

5.保存一下加入到MST中的顶点

6.更新lowxost,并且记录下更新后的直接前驱

7.prim算法结束后,max1数组存放的就是MST中各个顶点之间的权值最大的边。这时,我们需要对MST外的边加入到MST中并删除最大边的操作。这时候我们需要一个双层循环,遍历每一个MST外的边,当然,必须保证是MST外的边,那个判断语句就是防止是MST种的边,至于为什么i != pre[j] 和 j != pre[i]同时存在,是为了防止出现i=3,j=2和j=2,i=3,这种情况。这个判断一定要正确,要不然就会出现错误。


代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<algorithm>
using namespace std;
#define N 510
int map[N][N], lowcost[N], pre[N], max1[N][N], stack[N];
bool visit[N];
int n, m, sum;

void prim() //默认1在MST中
{
	int temp, k;
	int top; //保存最小生成树的结点

	memset(visit, false, sizeof(visit)); //初始化
	visit[1] = true;
	sum = 0;
	top = 0;
	for(int i = 1; i <= n; ++i)
	{
		pre[i] = 1;
		lowcost[i] = map[1][i];
	}
	lowcost[1] = 0;
	stack[top++] = 1; //保存MST的结点

	for(int i = 1; i <= n; ++i)
	{
		temp = INT_MAX;
		for(int j = 1; j <= n; ++j)
			if(!visit[j] && temp > lowcost[j])
				temp = lowcost[k = j];
		if(temp == INT_MAX)
			break;
		visit[k] = true;
		sum += temp;
		for(int j = 0; j < top; ++j) //新加入点到MST各点路径最大值
			max1[stack[j]][k] = max1[k][stack[j]] = max(max1[stack[j]][pre[k]], temp);
		stack[top++] = k; //保存MST的结点

		for(int j = 1; j <= n; ++j) //更新
			if(!visit[j] && lowcost[j] > map[k][j])
			{
				lowcost[j] = map[k][j];
				pre[j] = k; //记录直接前驱
			}
	}
}

int main()
{
	int ncase;
	int start, end, cost;
	int minn;
	scanf("%d", &ncase);
	while(ncase--)
	{
		for(int i = 1; i < N; ++i) //初始化不为0,1必须用循环。。。。
			for(int j = 1; j < N; ++j)
			{
				map[i][j] = INT_MAX;
				max1[i][j] = 0;
			}
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= m; ++i)
		{
			scanf("%d%d%d", &start, &end, &cost);
			//if(cost < map[start][end])(POJ竟然出现重边的时候不选择最小的~~~)
			map[start][end] = map[end][start] = cost;
		}
		prim();
		minn = INT_MAX;
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				if(i != j && i != pre[j] && j != pre[i]) //枚举MST以外的边
					minn = min(minn, map[i][j] - max1[i][j]); //求出{MST外加入边-MST环上权值最大边}最小值
		if(minn != 0)
			printf("No\n");
		else
			printf("Yes\n");
	}
	return 0;
} 


  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值