poj 2831(次小生成树)

题意:给你一幅图,再给你Q个询问,每个询问为id cost,即如果将id这条边的边权改为cost的话,这条边是否可能是最小生成树中的一条边

解题思路:将第i条边(u,v)的权值修改的话,要判断是否是最小生成树中的一条边,首先要把它加入进去,此时必定会引起原来的生成树成环,所以必定要擦去一条边,擦去的是哪一条边,这就利用到了次小生成树的原理了。

之前写过一个次小生成树的题,现在回过头看,感觉又有点不对了。

这里再总结一次:

首先肯定是构造一颗最小生成树,接下来就是枚举不在生成树里的边,假定为(u,v),此时应该把它加入到生成树中,但这样肯定会形成u->v的环路,此时肯定要删除u->v这条环路里的边,删哪一条呢?肯定是除了边(u,v)外的最大边。

现在是如何找到这条最大边,可以采用dp的思想,即dp[i][j]表示在树上i->j的最大值(注意,由于是树,肯定i->j的路径是唯一的)。我们在做Prim算法时,每次都是加入一个顶点S,我们还应该记录下顶点S加入到生成树里,它与谁相连,即我们在更新low[]数组时要记录的。这样,我们可以得到状态方程:dp[i][j] = dp[j][i] = max(dp[j][pre[i]],low[i]),此时i为即将要加入的点,而j是已经在生成树顶点集合里的点。

完成了一次Prim算法,同样也可以将dp数组更新好了,那么回到最开始的问题,删除的边我们就可以直接用dp[u][v]。


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

const int maxn = 100005;
const int inf = 0x3f3f3f3f;
struct Edge
{
	int u,v,c;
}edge[maxn];
int n,m,q;
int map[1005][1005],path[1005][1005];
int pre[1005],low[1005];
bool vis[1005],used[1005][1005];

int Prim()
{
	int k = 1,ans = 0;
	memset(path,0,sizeof(path));
	memset(vis,false,sizeof(vis));
	memset(used,false,sizeof(used));
	vis[k] = true;
	for(int i = 1; i <= n; i++)
	{
		low[i] = map[k][i];
		pre[i] = k;
	}
	for(int i = 1; i < n; i++)
	{
		int MIN = inf;
		for(int j = 1; j <= n; j++)
			if(vis[j] == false && low[j] < MIN)
			{
				MIN = low[j];
				k = j;
			}
		ans += MIN;
		vis[k] = true;
		used[k][pre[k]] = used[pre[k]][k] = true;
		for(int j = 1; j <= n; j++)
		{
			if(vis[j] == true && j != k)
				path[j][k] = path[k][j] = max(path[j][pre[k]],low[k]);
			if(vis[j] == false && low[j] > map[k][j])
			{
				low[j] = map[k][j];
				pre[j] = k;
			}
		}
	}
	return ans;
}

int main()
{
	while(scanf("%d%d%d",&n,&m,&q)!=EOF)
	{
		memset(map,inf,sizeof(map));
		for(int i = 1; i <= m; i++)
		{
			scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].c);
			map[edge[i].u][edge[i].v] = map[edge[i].v][edge[i].u] = min(map[edge[i].u][edge[i].v],edge[i].c);
		}
		int ans = Prim();
		while(q--)
		{
			int id,cost;
			scanf("%d%d",&id,&cost);
			if(path[edge[id].u][edge[id].v] >= cost)
				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、付费专栏及课程。

余额充值