题目描述
一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界。
现在他们想要去公园玩耍,但是他们的经费非常紧缺。
他们将乘车前往公园,为了减少花费,他们决定选择一种合理的乘车方式,可以使得他们去往公园需要的所有汽车行驶的总公里数最少。
为此,他们愿意通过很多人挤在同一辆车的方式,来减少汽车行驶的总花销。
由此,他们可以很多人驾车到某一个兄弟的家里,然后所有人都钻进一辆车里,再继续前进。
公园的停车场能停放的车的数量有限,而且因为公园有入场费,所以一旦一辆车子进入到公园内,就必须停在那里,不能再去接其他人。
现在请你想出一种方法,可以使得他们全都到达公园的情况下,所有汽车行驶的总路程最少。
输入格式
第一行包含整数 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
S−T次,因为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;
}