Poj·Picnic Planning

初见安~这里是传送门:Poj P1639

Description

The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible ability to cram an unlimited number of themselves into even the smallest vehicle. During the off-season, the brothers like to get together for an Annual Contortionists Meeting at a local park. However, the brothers are not only tight with regard to cramped quarters, but with money as well, so they try to find the way to get everyone to the party which minimizes the number of miles put on everyone's cars (thus saving gas, wear and tear, etc.). To this end they are willing to cram themselves into as few cars as necessary to minimize the total number of miles put on all their cars together. This often results in many brothers driving to one brother's house, leaving all but one car there and piling into the remaining one. There is a constraint at the park, however: the parking lot at the picnic site can only hold a limited number of cars, so that must be factored into the overall miserly calculation. Also, due to an entrance fee to the park, once any brother's car arrives at the park it is there to stay; he will not drop off his passengers and then leave to pick up other brothers. Now for your average circus clan, solving this problem is a challenge, so it is left to you to write a program to solve their milage minimization problem.

矮人虽小却喜欢乘坐巨大的轿车,轿车大到可以装下无论多少矮人。某天,N(N≤20)个矮人打算到野外聚餐。为了
集中到聚餐地点,矮人A 有以下两种选择
1)开车到矮人B家中,留下自己的轿车在矮人B家,然后乘坐B的轿车同行
2)直接开车到聚餐地点,并将车停放在聚餐地。虽然矮人的家很大,可以停放无数量轿车,但是聚餐地点却最
多只能停放K辆轿车。
现在给你一张加权无向图,它描述了N个矮人的家和聚餐地点,要你求出所有矮人开车的最短总路程。 

Input

Input will consist of one problem instance. The first line will contain a single integer n indicating the number of highway connections between brothers or between brothers and the park. The next n lines will contain one connection per line, of the form name1 name2 dist, where name1 and name2 are either the names of two brothers or the word Park and a brother's name (in either order), and dist is the integer distance between them. These roads will all be 2-way roads, and dist will always be positive.The maximum number of brothers will be 20 and the maximumlength of any name will be 10 characters.Following these n lines will be one final line containing an integer s which specifies the number of cars which can fit in the parking lot of the picnic site. You may assume that there is a path from every brother's house to the park and that a solution exists for each problem instance.

Output

Output should consist of one line of the form 
Total miles driven: xxx 
where xxx is the total number of miles driven by all the brothers' cars.

Sample Input

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

Sample Output

Total miles driven: 183

Sol

真的被毒瘤到了——这么毒瘤的题竟然作为考试的第一题,太打击人了好吧……

首先这个题可以翻译成这个意思——求出一颗无向图的最小生成树,满足Park节点的度数<=限定度数,也就是停放的车的数量

很好证明——因为假设点u的人开车一路到Park,这一路上的所有矮人坐同一辆车,这就是一条Park的度;并且因为要走最短的路,如果存在别的点的矮人到达了点u而不走u到Park的路径和u同乘一辆车,那么走的就不是最短路了。而走同一条路,就相当于走的边只算一次了,所以是求最小生成树。并且在求树的前提下,每个点都是连通的。

因为节点Park限定了度数,所以我们只能分离开考虑。先在删去Park点过后的各个连通块内求出最小生成树,然后开始考虑从各个连通块向Park连边。每个连通块至少要连一条边,我们就考虑Park连到这个连通块的最短的那条边。【在上述证明下,连该连通块内的任意一点效果都一样,所以我们只考虑从边权】。

而后,我们假设规定度数为K,现在有tot个连通块。【题目保证有解,所以一定有tot <= K】也就是说我们还有K - tot条边可以连。这时候就可以替换掉一些边——设点u在它的连通块内最小生成树上连出去的边权为w1,而存在从Park直接连到点u的边边权为w2且w2 < w1,那么我们就可以替换掉w1的那条边,累计的答案ans -= (w1 - w2)。当然,我们要找w1和w2差值最大的边进行替换。所以我们还需要预处理出一个量——点u到Park路径上的最大边权。【这里本意是不考虑与Park相连的边,但是就算考虑了,我们也可以因为差值的意义判定而筛掉的。】因为我们此时的替换操作是在改变我们的这个图,每次的最大边权都在改变,所以每次我们都要树形dp求一边最大边权,并且用一个bool矩阵记录这条边我们是否选用

最后输出答案即可。

实现起来有点恶心……因为字符串还要映射处理一下。

感觉自己的代码还是比较简洁了吧……我看的题解的都是100+的样子……【我的不到90行!!】

#include<iostream>
#include<map>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define maxn 25
using namespace std;
map<string, int> mp;
string u, v;
int m, n = 0, K, w[25][25], ed, tot, bridge[25], point[25];
int ans = 0;
bool in[25][25];
struct edge {
	int u, v, w;
	bool operator < (const edge &x) const {return w < x.w;}
}e[maxn << 4], dp[maxn];

int fa[maxn];
int get(int x) {return x == fa[x]? x : fa[x] = get(fa[x]);}

void Kruskal() {
	register int u, v;
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++) {
		u = e[i].u, v = e[i].v;
		if(u == ed || v == ed) continue;//不和Park连边 
		if(get(u) == get(v)) continue;
		fa[get(u)] = get(v); ans += e[i].w;
		in[u][v] = in[v][u] = true;//标记选中 
	}
}

void dfs(int u, int pre) {
    for (int i = 1; i <= n; i++) {
        if (i == pre || !in[u][i] || i == ed) continue;
        if (dp[u].w > w[u][i]) dp[i] = dp[u];
        else dp[i].u = u, dp[i].v = i, dp[i].w = w[u][i];
        dfs(i, u);
    }
}

int main() {
	ios::sync_with_stdio(false);
	memset(w, 0x3f, sizeof w);
	cin >> m;
	for(int i = 1; i <= m; i++) {
		cin >> u >> v;
		if(!mp.count(u)) mp[u] = ++n;
		if(!mp.count(v)) mp[v] = ++n;
		if(u == "Park") ed = mp[u]; else if(v == "Park") ed = mp[v];//ed记录Park的编号 
		cin >> w[mp[u]][mp[v]] , w[mp[v]][mp[u]] = w[mp[u]][mp[v]];
		e[i].u = mp[u], e[i].v = mp[v], e[i].w = w[mp[u]][mp[v]];
	}
	
	sort(e + 1, e + 1 + m);
	Kruskal();//Kruskal求各个连通块内的最小生成树 
	memset(bridge, 0x3f, sizeof bridge);//bridge[i]为从Park到连通块 i的最短边边权 
	for(int i = 1; i <= n; i++) if(fa[i] == i) tot++;//tot记录连通块数量【这里算了Park,下文解释 
	
	for(int i = 1; i <= n; i++)
		if(w[ed][i] < bridge[get(i)])//记录Park到连通块最短变边并标记是哪个点 
			bridge[get(i)] = w[ed][i], point[get(i)] = i;
	
	for(int i = 1; i <= n; i++) //累加ans 
		if(point[i]) ans += w[ed][point[i]], in[ed][point[i]] = in[point[i]][ed] = true;
	
	cin >> K;
	for(int i = tot; i <= K; i++) {//因为tot如果没有计上Park,那么这里就要+1,所以就算上了。 
		memset(dp, -1, sizeof dp);//每次都重新跑一遍树形dp的 
		dfs(ed, -1);
		
		register int minedge = -(0x3f3f3f3f), minpoint;//minnedge为差值,minpoint为差值最大的点 
		for(int j = 1; j <= n; j++) {//替换点j到Park的边,找最大差值的替换 
			if(minedge < dp[j].w - w[ed][j] && j != ed)
				minedge = dp[j].w - w[ed][j], minpoint = j;
		}
		
		if(minedge <= 0) break;//ans减去这个差值已经没有意义了 
		in[ed][minpoint] = in[minpoint][ed] = 1;//更改边的选择情况 
        in[dp[minpoint].u][dp[minpoint].v] = in[dp[minpoint].v][dp[minpoint].u] = 0;
        ans -= minedge;//ans减去这个差值 
	}
	
	printf("Total miles driven: %d\n", ans);
	return 0;
}

超级毒瘤……思路比较简单,但是码起来就会发现会开很多变量,就越写越乱了…………

迎评:)
——End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值