修路好像不是很容易(最小生成树+二分)

该博客讨论了一种在图论问题中寻找最小生成树的优化策略。具体来说,当图中的边有两种类型(带有公厕和不带有公厕),且需要确保一定数量的边带有公厕时,如何通过调整边的权重来找到最小成本的解决方案。博主提出使用二分搜索配合修正指数的方法,以找到满足条件的最小花费。通过调整边的权重,保证最小生成树中特殊边的数量达到目标值,从而解决了这个问题。
摘要由CSDN通过智能技术生成

修路好像不是很容易

TimeLimit: 50000/15000 MS (Java/Others) MemoryLimit: 32768/32768 K(Java/Others) 64-bit integer IO format:%I64d Problem Description

道一打算在他的城市修路,路有n-1条(M条路选出n-1条)必须经过n个点,每条路有两种情况,一是有顺便造了公厕的,二是没有造公厕的,每条路都有它自己的花费,道一想让k条路是有公厕的,并且花费最少。
Input 每组数据第一行包括 N (1 <= N <= 50,000), 道路数量M(N-1 <= M <= 100,000)
和要求有公厕的道路数量K (0 <= K <= N-1).接下来M行,每行包括 a, b, c, x (0 <= a, b <= N-1,
a != b, 1 <= c <= 100, x 属于{0,1},a,b表示道路连接的两个点,c表示道路的花费,x表示有公厕或者没公厕.
x=0 代表有公厕,x=1代表没公厕)。 Output
对于每组数据,输出最小花费。 SampleInput
2 2 1
0 1 1 1
0 1 2 0
2 2 0
0 1 1 1
0 1 2 0
SampleOutput
Case 1: 2
Case 2: 1

首先,因为要输出花费,所以要求最小生成树,但是题目中路的类型有两种,要保证生成树中特殊路的数量大于等于目标数量k显然不是贪心,用贪心的话,优先生成k条特殊路,若遇上以下情况会wan。

3 4 1
1 2 9 0
1 2 1 1
0 1 11 0
0 1 8 1
会先生成1 2 9 0
再生成 0 1 8 1
解为 17
最优解应该为 12

如果新增一个修正指数int Modified
使特殊边的权重减少(或增加)Modified
就能减小(或增加)特殊边的优先级
此时再生成最小生成树,就能含有不同特殊边数目的最小生成树。
观察可以发现最小生成树含有特殊边的数目和修正指数正相关。故可以用二分来求解修正指数 Modified

while (l <= r)
		{
			m = (l + r) / 2;
			Modified = m;
			Weight = 0;
			Nowsum = 0;
			if (Minimum_spanning_tree())
			{
				l = m + 1;
			}
			else
			{
				r = m - 1;
				if (Nowsum == Aimsum)
					ans = Weight + Aimsum * Modified;
			}
		}
		printf("Case %d: %lld\n", k++, ans);
	}
	int Minimum_spanning_tree()
{
	Unionclear();
	for (int i = 0; i < Edgesum; i++)
	{
		if (Edge[i].use == 0) { Edge[i].weight -= Modified; }
	}
	sort(Edge, Edge + Edgesum, cmp);
	Unionsum = Pointsum;
	int i = 0;
	while (i < Edgesum) {
		if (Find(Edge[i].x) != Find(Edge[i].y)) {
			UnionConnect(Edge[i].x, Edge[i].y);
			if (Edge[i].use == 0) Nowsum++;
			Weight += Edge[i].weight;
		}i++;
		if (Unionsum == 1) break;
	}
	for (int i = 0; i < Edgesum; i++)
	{
		if (Edge[i].use == 0)
			Edge[i].weight += Modified;
	}
	return Nowsum < Aimsum;
}

若Nowsum>=Aimsum(不是等于,可能有一种样例,使得我们不可能生成Nowsum==Aimsum)。我们就可以认为这个方案可行,所以记录它的 权重 ,但是这个可行的方案可能不是最优解,但是若Nowsum<Aimsum,则这个方案不可行,因为最小生成树的权重和修正指数程正相关,所以我们找到它的边界的最小上界就行。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
#include<string>
#include <math.h>
#include<cstring>
using namespace std;
#define MaximumOfPoint 100010
#define MaximumOfEdge  100010
int Unionsum;
int Edgesum;
int Pointsum;
int Aimsum; int Modified;
int per[MaximumOfPoint];//并查集
long long Weight;
typedef struct EDGE {
	int x, y, weight;
	int  use;
	EDGE(int a, int b, int c,int mode) :weight(c), x(a), y(b), use(mode) {}
	EDGE() :weight(), x(), y(), use() {}
}edge;//边类
edge Edge[MaximumOfEdge];//边集
int Find(int x)
{
	int tt = x;
	while (x != per[x]) x = per[x];
	while (tt != x)
	{
		int t = per[tt];
		per[tt] = x;
		tt = t;
	}
	return x;
}//并查集用
void UnionConnect(int x, int y)
{
	x = Find(x);
	y = Find(y);
	if (x != y) {
		per[x] = y;
		Unionsum--;
	}
}//把x所在集合连到y
bool cmp(edge x, edge y) {
	if (x.weight != y.weight) {
		return x.weight < y.weight;
	}
	return x.use < y.use;
}
void Unionclear()
{
	for (int i = 0; i < Pointsum; i++)
	{
		per[i] = i;
	}
}
int Nowsum = 0;
int Minimum_spanning_tree()
{
	Unionclear();
	for (int i = 0; i < Edgesum; i++)
	{
		if (Edge[i].use == 0) { Edge[i].weight -= Modified; }
	}
	sort(Edge, Edge + Edgesum, cmp);
	Unionsum = Pointsum;
	int i = 0;
	while (i < Edgesum) {
		if (Find(Edge[i].x) != Find(Edge[i].y)) {
			UnionConnect(Edge[i].x, Edge[i].y);
			if (Edge[i].use == 0) Nowsum++;
			Weight += Edge[i].weight;
		}i++;
		if (Unionsum == 1) break;
	}
	for (int i = 0; i < Edgesum; i++)
	{
		if (Edge[i].use == 0)
			Edge[i].weight += Modified;
	}
	return Nowsum < Aimsum;
}
int main()
{
	int n, m, i, x, y, w, mode, k;
	k = 1;
	while (~scanf("%d%d%d", &n, &m, &Aimsum))
	{
		i = 0;
		Pointsum = n; 
		Edgesum = m;
		Unionsum = n;
		while (i < m)
		{
			scanf("%d%d%d%d", &x, &y, &w, &mode);
			Edge[i] = (edge(x, y, w, mode));
			i++;
		}
		int l = -102, m, r = 102; long long ans = 0, o;
		while (l <= r)
		{
			m = (l + r) / 2;
			Modified = m;
			Weight = 0;
			Nowsum = 0;
			if (Minimum_spanning_tree())
			{
				l = m + 1;
			}
			else
			{
				r = m - 1;
				if (Nowsum == Aimsum)
					ans = Weight + Aimsum * Modified;
			}
		}
		printf("Case %d: %lld\n", k++, ans);
	}
}

题目来自FJUT-1510

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值