【POJ 2831】 Can We Build This One?(prim 最小生成树变形)

6 篇文章 0 订阅

Description

某国计划修建若干高速公路,用来连接国内N个城市,经过一番细致的考察后,政府迁出了M条待建的公路 
每条公路用三个整数(x,y,z)来,即城市X与城市Y之间可以修一条高速公路,需要Z的花费。出于节约,政府希望从这些公路出选一些出来修建,使总开支最小。并保证建造后任意两个城市之间都可以直接或间接相连。但往往只考虑费用并不能得到最有价值的方案,例如城市A与城市B之间活动较频繁为了方便这两个城市间交通运输,应修建一条AB间直达的高速公路。于是政府考试将某些公路的建造开支降低 ,使得修筑这条公路后,仍然可以得到一个总开支最低(甚至比原来更低)方案,你的任务就是判断这样的计划可不可行。

Input

第一行三个整数N,M,Q(1<=N<=10000,N-1<=M<=100000,0接下来M行,每行三个整数(X,Y,Z)描述一条可以建造的公路。 
(1y,0<=C<=10000) 
最后Q行,每行两个整数i,x(1<=i<=M,0<=x)描述一个修改计划,即要你判断如果 将第i条公路的修建费用降低为x元,第i条公路是否可以修建。

Output

按顺序输出Q行,每行输出"Yes"或"No",Yes表示可以建造,No表示不可能

Sample Input

3 4 3
1 2 10
1 3 6
2 3 4
1 3 7
4 6
1 7
1 5

Sample Output

Yes
No
Yes

HINT

Source


思路:prim。

考虑prim的算法过程:每次加入一个点,并且加入该点的条件是dis[i]是还没加入点的dis[]中最小的。

所以执行prim时,用数组no[]保存某点加入的次序,numm[i]保存第i个加入的点的id

查询边(a,b)时(假设a的加入次序先于b),则枚举no[a]+1 ~ no[b]加入的点的dis[],若存在x<=dis[k],原prim算法过程可被打破,边(a,b)会被选择,

因为该边是当前最佳的边;否则还是原算法过程进行。 


#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iomanip>
using namespace std;

const int maxd = 1000001;
const int N = 1005;
const int M = 1000001;

int map1[N][N], dis[N], vis[N], no[N], numm[N];
struct Path {
	int a, b, c;
} p[M];

void prim(int n) {
	int cur;
	for (int i = 1; i <= n; i++)
		dis[i] = maxd;
	memset(vis, 0, sizeof(vis));
	cur = 1;
	dis[cur] = 0;
	vis[cur] = 1;
	no[cur] = 1;
	numm[1] = cur;
	//找n-1轮
	for (int i = 2; i <= n; i++) {
		//枚举上一次加入的点与各个点的距离,更新最小距离dis[i]
		for (int j = 1; j <= n; j++) {
			if (vis[j] == 0 && dis[j] > map1[cur][j])
				dis[j] = map1[cur][j];
		}
		//选出最短的边,把该边连接的点加入结果集
		int mind = maxd;
		for (int j = 1; j <= n; j++) {
			if (vis[j] == 0 && dis[j] < mind) {
				mind = dis[j];
				cur = j;
			}
		}
		vis[cur] = 1;
		no[cur] = i;
		numm[i] = cur;
	}
}

void init(int n) {
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			map1[i][j] = maxd;
}

int main() {
	int n, m, q, pi, x;
	scanf("%d%d%d", &n, &m, &q);
	init(n);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].c);
		if (map1[p[i].a][p[i].b] > p[i].c)
			map1[p[i].a][p[i].b] = map1[p[i].b][p[i].a] = p[i].c;
	}
	//prim走一遍,保存no[],numm[],dis[]
	prim(n);

	for (int i = 1; i <= q; i++) {
		scanf("%d%d", &pi, &x);
		int a = no[p[pi].a], b = no[p[pi].b], flag = 0, bno, sno;
		if (a > b) {
			bno = a;
			sno = b;
		} else {
			bno = b;
			sno = a;
		}
		//枚举p[pi].a 和 p[pi].b两点之间加入的点
		//若x<=dis[],则该边为这轮的最优边,可成为最小生成树中的一边
		for (int k = sno + 1; k <= bno; k++) {
			if (x <= dis[numm[k]]) {
				flag = 1;
				break;
			}
		}
		if (flag == 1)
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值