最小生成树唯一吗_2509 野餐规划 (最小生成树 替换边)

题目描述

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

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

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

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

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

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

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

输入

第一行包含整数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号点(Park)的度数不超过s。

首先,可以计算,如果没有1号节点,可以分成多少个连通块,数量设为t,那么1号节点至少要连t条边。

如果t>s,无解。本题一定有解,则1号节点可以尝试增加连s-t条边。

优先按照最小边原则选1出发的t条边连通t个连通块,然后最多做s-t次调整。

每次调整,枚举未使用的1出发的边w,dfs这条边形成的环上的最长边z,求出最大差值(z-w),如果最大差值(z-w)<=0则调整结束;否则用w边替换z边。

例如:

ab0d1f17686b145ff9ecf2939a88a63d.png

这部分的dfs可以设置fa数组,记录以1为根,每个子树的父亲,方便调整。

#includeusing namespace std;const int oo=0x3f3f3f3f;int n,m,s,t,g[25][25],mark[25],d[25],fa[25],ans;int u,v;map<string,int>mp;void mst(int x){    //以x为初始集合,做最小生成树    d[x]=0; fa[x]=1; //x的父亲是1    for(int i=2; i<=n; i++){        d[i]=g[x][i];        if(mark[i]==0) fa[i]=x;    }    for(int i=2; i<=n; i++){        int x=0,y=oo;        for(int j=2; j<=n; j++)          if(mark[j]==0 && d[j]              x=j; y=d[j];          }        if(x==0) break;        ans+=y; mark[x]=1;        for(int j=2; j<=n; j++)          if(mark[j]==0 && g[x][j]              d[j]=g[x][j]; fa[j]=x;          }    }}void dfs(int x){    int y=fa[x];    if(g[x][y]>v){        u=x; v=g[x][y];    }    if(y!=1) dfs(y);}void dfs2(int x,int p){    int y=fa[x];    fa[x]=p;    if(x!=u) dfs2(y,x);}int main(){    cin>>m;    mp["Park"]=1; n=1;    memset(g,0x3f,sizeof(g));    for(int i=1; i<=m; i++){        string s1,s2; int a,b,c;        cin>>s1>>s2>>c;        if(!mp[s1]) mp[s1]=++n;        if(!mp[s2]) mp[s2]=++n;        a=mp[s1]; b=mp[s2];        g[a][b]=g[b][a]=c;    }    cin>>s;    for(int i=2; i<=n; i++){    //选择最小的t条1出发的边,做t次最小生成树        int x=0,y=oo;        for(int j=2; j<=n; j++)          if(mark[j]==0 && g[1][j]              x=j; y=g[1][j];          }        if(x==0) break;        ans+=y; mark[x]=1;        mst(x); t++;    }    s-=t;    while(s){        int x=0,y=0,mid=0;        for(int i=2; i<=n; i++){            u=0,v=0; //全局变量            if(g[1][i]1){                dfs(i); //从1->i逆向搜到最大边u处                if(v-g[1][i]>y){                    x=i; y=v-g[1][i]; mid=u;                }            }        }        if(x==0) break;        ans-=y; s--; u=mid;        dfs2(x,1); //把1->x逆向到u处点的fa值逆向    }    printf("Total miles driven: %d",ans);    return 0;}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值