347. 野餐规划 (换边法,《算法竞赛进阶指南》)

347. 野餐规划 - AcWing题库

一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界。

现在他们想要去公园玩耍,但是他们的经费非常紧缺。

他们将乘车前往公园,为了减少花费,他们决定选择一种合理的乘车方式,可以使得他们去往公园需要的所有汽车行驶的总公里数最少。

为此,他们愿意通过很多人挤在同一辆车的方式,来减少汽车行驶的总花销。

由此,他们可以很多人驾车到某一个兄弟的家里,然后所有人都钻进一辆车里,再继续前进。

公园的停车场能停放的车的数量有限,而且因为公园有入场费,所以一旦一辆车子进入到公园内,就必须停在那里,不能再去接其他人。

现在请你想出一种方法,可以使得他们全都到达公园的情况下,所有汽车行驶的总路程最少。

输入格式

第一行包含整数 n,表示人和人之间或人和公园之间的道路的总数量。

接下来 n 行,每行包含两个字符串 A、B 和一个整数 L,用以描述人 A 和人 B 之前存在道路,路长为 L,或者描述某人和公园之间存在道路,路长为 L。

道路都是双向的,并且人数不超过 20,表示人的名字的字符串长度不超过 10,公园用 Park 表示。

再接下来一行,包含整数 s,表示公园的最大停车数量。

你可以假设每个人的家都有一条通往公园的道路。

输出格式

输出 Total miles driven: xxx,其中 xxx 表示所有汽车行驶的总路程。

输入样例:
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 43
Bernardo Park 19
Bernardo Clemenzi 82
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 109
Park Herb 24
Herb Eduardo 79
3
输出样例:
Total miles driven: 183

 解析:

首先 ,去掉1号节点之后, 无向可能会分成若干个连通块。可以用深度优先遍历划分出图中的每个连通块,设连通块共有 T 个,若T>S,则本题无解。
对于每个连通块,在这个连通块内部求出它的最小生成树,然后从连通块中选出一个节点P与1号节点相连,其中无向边(1,p)的权值尽量小。
此时,我们已经得到了原无向图的一棵生成树,1号节点的度数为T。我们还可以尝试改动S-T条边,让答案更优。
考虑无向图中从节点1出发的每条边(1,x), 边权为z。 如果(1.x)还不在当前的生成树中,那么继续找到当前生成树中从X到1的路径上权值最大的边(u,v),边权为 w. 求出使得w-z最大的点Xo。 若Xo 对应的 Wo -Zo>0,则从树中删掉边(uo,vo). 加入边(1,xo)· 答案就会变小Wo-Zo。
重复上一步S-T次,或者直到Wo-Zo<=0,就得到了题目所求的最小生成树。

本题的难点在于如何换边,具体的操作过程可以参考这道题: 1148. 秘密的牛奶运输 (次小生成树)-CSDN博客

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
using namespace std;
typedef long long LL;
const int N = 25, M = 405;
int n,m, k;
struct Edge {
	int a, b, c, f;
	bool operator<(const Edge& t)const {
		return c < t.c;
	}
}edge[M];
int dist[N][N], fa[N];
int h[N], e[M], w[M], ne[M], idx;
map<string, int>mp;

int find(int a) {
	if (fa[a] == a)return a;
	return fa[a] = find(fa[a]);
}

void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u, int f, int mx, int d[]) {
	d[u] = mx;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (j == f)continue;
		dfs(j, u, max(mx, w[i]), d);
	}
}

int main() {
	cin >> m ;
	int tt = 0;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= m; i++) {
		string a, b;
		int c;
		cin >> a >> b >> c;
		if (!mp.count(a))mp[a] = ++n;
		if (!mp.count(b))mp[b] = ++n;
		if (a == "Park")tt = mp[a];
		if (b == "Park")tt = mp[b];
		edge[i] = { mp[a],mp[b],c };
	}
	cin >> k;

	sort(edge + 1, edge + 1 + m);

	for (int i = 1; i <= n; i++) fa[i] = i;
	int sum = 0;
	for (int i = 1; i <= m; i++) {
		int a = edge[i].a, b = edge[i].b, c = edge[i].c;
		if (a == tt || b == tt)continue;
		int pa = find(edge[i].a), pb = find(edge[i].b);
		if (pa != pb) {
			edge[i].f = 1;
			sum += c;
			fa[pa] = pb;
			add(a, b, c), add(b, a, c);
		}
	}

	for (int i = 0; i<=m && k > 0; i++) {
		int a = edge[i].a, b = edge[i].b, c = edge[i].c;
		if (a != tt && b != tt)continue;
		int pa = find(edge[i].a), pb = find(edge[i].b);
		if (pa != pb) {
			k--;
			edge[i].f = 1;
			sum += c;
			fa[pa] = pb;
			add(a, b, c), add(b, a, c);

		}
	}
	
	for (int i = 1; i <= n; i++)dfs(i, 0, 0, dist[i]);
	vector<int>q;
	for (int i = 1; i <= m ; i++) {
		int a = edge[i].a, b = edge[i].b, c = edge[i].c;
		if ((a != tt && b != tt) || edge[i].f)continue;
		if (c < dist[a][b] ) {
			q.push_back(dist[a][b] - c);
		}
	}
	sort(q.begin(), q.end());
	reverse(q.begin(), q.end());
	for (int i = 0; i < q.size() && k>0; i++) {
		sum -= q[i];
		k--;
	}
	printf("Total miles driven: %d\n", sum);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值