《算法竞赛进阶指南》0x62 T2 Picnic Planning

题目传送门

题目描述

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

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

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

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

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

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

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

输入格式

第一行包含整数 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

题解

考虑建图问题,由于边连接的是人名,即字符串,所以我们可以拿map映射人名给予下标,然后来用邻接矩形存边
这道题涉及到一些超出最小生成树范畴的思想,我们需要考虑删除1节点后以及与1节点相关的边之后,无向图会被分为多少个连通块,假设为T,我们首先在每一个连通块之间跑最小生成树,建立T+1棵树(1表示1号根)
我们将这T棵树中的一个节点与1号节点用一条边连接(这条边的权值应最小),于是一棵基本的树就建成了
这时考虑最优性,对于每个连通块内部,边权最小,对于每个连通块到1号节点之间,边权最小
瑕疵在于有些连通块内部的边,我们可以将其替换为连接1号点和连通块内部一点的边
这个操作最多进行 S − T S-T ST次,因为1号节点连接的边不能超过 S S S
考虑用 ( 1 , x ) (1,x) (1,x)代替 x x x 1 1 1的路径上权值最大的边,每次我们找出一对能使权值和减少的更多的边进行修改,如果无法进行更优的更改,我们就直接放弃之后的修改,break跳出循环,具体的细节见代码

code
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
map<string,int> mp;
map<pair<int,int>,bool> ff;
int f[N][N],vis[N],fa[N];
int n,s,cnt,tot;
struct node
{
	int u,v,w;
}edge[N];
bool cmp(node x,node y)
{
	return x.w<y.w;
}
void dfs(int x)
{
	for(int i=2;i<=tot;i++)
		if(f[x][i]&&!vis[i])
		{
			vis[i]=cnt;
			dfs(i);
		}
}
int U[N],V[N],dis[N];
void dfs1(int x,int fa)
{
	for(int i=2;i<=tot;i++)
	{
		if(i==fa||!ff[make_pair(x,i)]) continue;
		if(dis[i]==-1)
		{
			if(dis[x]>f[x][i]) U[i]=U[x],V[i]=V[x],dis[i]=dis[x];
			else U[i]=x,V[i]=i,dis[i]=f[x][i];
		}
		dfs1(i,x);
	}	
}
int get(int x)
{
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
int main()
{
	scanf("%d",&n);
	mp["Park"]=tot=1;
	int t=0;
	for(int i=1;i<=n;i++)
	{
		string a,b;
		int c;
		cin>>a>>b;
		scanf("%d",&c);
		if(!mp[a]) mp[a]=(++tot);//map映射 
		if(!mp[b]) mp[b]=(++tot);
		edge[++t].u=mp[a];//链式前向星存边 
		edge[t].v=mp[b];
		edge[t].w=c;
		f[mp[a]][mp[b]]=f[mp[b]][mp[a]]=c;//邻接矩阵连边 
	}
	scanf("%d",&s);
	for(int i=2;i<=tot;i++)//确定连通块的个数以及每个的大小 
		if(!vis[i])
		{
			vis[i]=++cnt;
			dfs(i);
		}
	sort(edge+1,edge+1+t,cmp);//对边权排序 
	int ans=0;
	for(int i=1;i<=n;i++) fa[i]=i;//并查集初始化 
	for(int i=1;i<=t;i++)//连通块内部连边 
	{
		int fx=get(edge[i].u),fy=get(edge[i].v);
		if(fx==1||fy==1||fx==fy) continue;
		fa[fx]=fy;
		ans+=edge[i].w;
		ff[make_pair(edge[i].u,edge[i].v)]=1;
		ff[make_pair(edge[i].v,edge[i].u)]=1;
	}
	for(int i=1;i<=cnt;i++)//连接1号节点 
	{
		int now=1e9,st=0;
		for(int j=2;j<=tot;j++)
			if(vis[j]==i)
				if(now>f[1][j]&&f[1][j])
				{
					now=f[1][j];
					st=j;
				}
		ans+=now;
		ff[make_pair(1,st)]=ff[make_pair(st,1)]=1;
	}
	int T=cnt;
	for(int i=T+1;i<=s;i++)//将连通块内部边改为与1节点相连的边 
	{
		int now=0,id;
		memset(U,0,sizeof(U));
		memset(V,0,sizeof(V));
		memset(dis,-1,sizeof(dis));
		dfs1(1,-1);
		for(int j=2;j<=tot;j++)
			if(now<dis[j]-f[1][j]&&f[1][j]) 
				now=dis[j]-f[1][j],id=j;
		if(now<=0) break;
		ans-=now;
		ff[make_pair(U[id],V[id])]=0;
		ff[make_pair(1,id)]=ff[make_pair(id,1)]=1;
	}
	printf("Total miles driven: %d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值