NYOJ118 修路方案

5 篇文章 0 订阅

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=118

题目分析:
上次也做了那个最小生成树的题了,但是当时只是看懂了那两个算法,没有深入理解,然后这道题试图用上次的套路做,结果悲剧的TLE了,我贡献了N个WA啊,只能说明我太执着了。唉~不说了说多了都是泪。我纠结了一整天,一整天啊~~~骂人抓狂
这道题其实上就是求次小生成树,判断次小生成树是不是和最小生成树一样大。加入一条不属于最小生成树的最小的边e,必然会有一个环,找出这个环中非e的长度最长的边,用e替换之即可。这里用一个二维数组就可以维护节点i到节点j的最长的边。当一个节点作为新节点v加入集合s的时候,s中已有的点与v的最长的边就是max(到其前驱节点的最长的边,前驱节点到v的边长)。
下面说说prim算法,其本质是把图分成两个集合,记以选入的最小生成树的集合为s,则与之对应的,其补集记为u,每一次迭代,其实就是选u中与集合s距离最近的点,加到s中,有了这个思想看代码也就不难了,我的代码都写上了注释,应该不难看懂。

#include<stdio.h>
#include<string.h>

const int N = 501;
int Map[N][N];//两点之间的距离
bool used[N];//节点是否用过
int dis[N];//集合外的点到集合s的最短距离
int pre[N];//集合外的点距离最近的集合s中的点
int MaxLen[N][N];//两个节点间的最大的边的长度

inline int max(const int a, const int b)
{
	return a > b ? a : b;
}

void Prim(int n)
{
	int i,j;
	int Min,MinI,pr;
	used[1] = true;
	//初始状态,只有1在集合内,其他点到集合的距离即为到1的距离
	for(i = 2; i <= n; ++i)
	{
		dis[i] = Map[1][i];
		pre[i] = 1;
	}
	for(i = 1; i < n; ++i)
	{
		Min = 1e7;
		//找出与集合s距离最近的点
		for(j = 2; j <= n; ++j)
		{
			if(!used[j] && Min > dis[j])
			{
				MinI = j;
				Min = dis[j];
			}
		}
		pr = pre[MinI];
		MaxLen[pr][MinI] = MaxLen[MinI][pr] = dis[MinI];
		//更新集合s到新加入的点的最长边
		for(j = 1; j <= n; ++j)
		{
			if(used[j])
				MaxLen[j][MinI] = MaxLen[MinI][j] = max(MaxLen[j][pr], dis[MinI]);
		}
		used[MinI] = true;
		//更新集合s到s外各点的距离
		for(j = 2; j <= n; ++j)
		{
			if(!used[j] && Map[j][MinI] < dis[j])
			{
				dis[j] = Map[j][MinI];
				pre[j] = MinI;
			}
		}		
	}
}

bool SecondMST(int n)
{
	int i,j;
	for(i = 1; i <= n; ++i)
	{
		for(j = i + 1; j <= n; ++j)
		{
			//说明节点i到j的边已经用过了
			if(pre[i] == j || pre[j] == i)
				continue;
			else if(MaxLen[i][j] == Map[i][j])
				return true;
		}
	}
	return false;
}

int main()
{
	int t,v,e;
	int a,b,c;
	int i,j;
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d %d", &v, &e);
		
		for(i = 1; i <= v; ++i)
			for(j = 1; j <= v; ++j)
				Map[i][j] = 1e7;

		memset(used, 0, sizeof(used));

		for(i = 0; i < e; ++i)
		{
			scanf("%d %d %d", &a, &b, &c);
			Map[a][b] = Map[b][a] = c;
		}
		Prim(v);
		if(SecondMST(v))
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值