poj1639

本文介绍了一种解决有限停车位情况下,求所有人到达公园且所有车辆路程之和最小的问题。首先通过Kruskal算法去除终点得到多个连通块的最小生成树,然后将终点与各连通块相连,最后通过动态规划进一步优化,确保在有限停车位限制下,路径和最小。该方法结合了图论和动态规划,有效地解决了实际场景中的路径优化问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

每个人都从家里开车出来去公园,去公园的路上可能经过别人的家里,这样子可以蹭别人的车,自己的车就不用费油了,假设一辆车子可以无限坐人,每个人家里可以停无限量车,但是公园只能最多停k辆车,求所有人都到达公园且所有车子走过的路程之和最小为多少。

输入

第一行一个整数n
接下来n行每行两个字符串name1,name2和一个数字z,表示name1家到name2家又一条路径权值为z的路,其中名字为park的是公园,也就是终点
最后一行一个整数k表示公园能够停车的数量

输出

Total miles driven: x
x为所有车子走过的路程总和

解题思路

如果公园可以无限停车那么求最小生成树就好了,但是终点停车数量有限制,需要转换思路,这里假设公园为终点。先想一种一定符合条件的生成树(不一定是最小生成树),这样的生成树可以按如下前两步:
1.去掉终点跑一次 Kruskal 得到若干个连通块的最小生成树(并查集区分连通块)。

2.将终点和每个连通块相连,相连的边取最短的边,这样子就一定是一个符合条件的生成树了。
这里前两步就够造了一个可行的生成树

hint:假设第一步去掉终点跑一次Kruskal之后得到的连通块数量为m个,那么第二步与连通块连的最短的边为m条,这m条一定是最小生成树的边。终点最多停K辆车,这样子还可以用终点与连通块中的点连最多K-m条边使得生成树的权值之和变的更小。

3.对于当前已经构造的生成树中通过dp将从终点到当前节点i路径上的最长边存储在dp数组中(不包括包含终点的边),同时也需要存储这条边的起点和终点。

4.不断重复第三步,直到更新了K-m条边或者没办法更新为止。

还没有懂可以看下面代码+注释

#include<iostream>
#include<map>
#include<algorithm>
#include<string.h>
using namespace std;

struct node//存储边信息
{
    int x,y,z;
    node(){};
    node(int a,int b,int c):x(a),y(b),z(c){}; 
    bool operator <(const node &e) const{
	return z<e.z;}
}edge[500];

const int maxn=22;
const int inf=0x3f3f3f3f;//无穷大
int g[maxn][maxn];//邻接矩阵存图
int tree[maxn][maxn];//存储当前生成树中有哪些边,tree[x][y]=1表示当前生成树中有e(x,y)这条边否则没有
int fa[maxn];//并查集存储连通块的根节点
int minedge[maxn];//minedge[x]!=inf时有效,此时x一定是某个连通块的根节点,minedge[x]表示终点到该节点的最短边长度
int linkpoint[maxn];//linkpoint[x]!=0时有效,此时x一定是某个连通块的根节点,linkpoint[x]表示重点到该节点最短边对应的节点
int ans,n,cnt,tot,m,k;
//ans表示所有车子走过的路径和
//n为题目中的n
//cnt为节点数
//tot为边数
//m为去掉终点后跑一次Krustra得到的连通块数量
//k为终点可以停的车子数量
node dp[maxn];//dp[x]表示从终点到点x路径上最长的边的信息(路径不包括包含终点的边)
map<string,int>mp;//输入时节点按照字符串输入,mp表示对应编号
int find(int x)//并查集
{
    return x==fa[x]?x:(fa[x]=find(fa[x]));
}
void Kruskal()//第一步,除去终点跑一次Krustra
{
    sort(edge+1,edge+1+tot);
    for(int i=1;i<=tot;i++)
    {
        int x=edge[i].x;
        int y=edge[i].y;
        int z=edge[i].z;
        if(x==1||y==1)//终点编号设置为1,所以包含1时跳过
        continue;
        if(find(x)!=find(y))
        {
            fa[find(x)]=find(y);
            tree[x][y]=1;
            tree[y][x]=1;
            ans+=z;
        }
    }
}
void dfs(int cur,int pre)//从终点开始边dfs边找到终点到各个节点对应路径上权值最大的边,cur表示当前节点,pre表示前一个节点
{
    for(int i=2;i<=cnt;i++)
    {
        if(i==pre||!tree[cur][i])//因为在当前构造生成树走的路径中找最长的边,而不是所有边中找,所以tree[cur][i]需要=1
        continue;
        if(dp[i].z==-1)//所有节点只会处理到一次,因为处理出来的生成树是一棵树
        {
            if(dp[cur].z>g[cur][i])
            {
                dp[i]=dp[cur];
            }else
            {   
                dp[i].x=cur;
                dp[i].y=i;
                dp[i].z=g[cur][i];
            }
        }
        dfs(i,cur);
    }
}
void parktoother()//包含2,3,4步
{
    for(int i=2;i<=cnt;i++)//第二部,处理出终点到各个连通块的最小值,最小值信息放在各个连通块的根节点x上
    {
        if(g[1][i]==inf)
        continue;
        int x=find(i);//x为i节点对应连通块的根节点
        if(g[1][i]<minedge[x])//minedge初始化为无穷大
        {
            minedge[x]=g[1][i];//minedge存放终点到连通块的最小值大小
            linkpoint[x]=i;//linkpoint存储终点到连通块最小值边对应的点
        }
    }
    for(int i=1;i<=cnt;i++)//第二步,连接终点与各个连通块
    {
        if(linkpoint[i])// ||minedge[i]
        {
            int x=linkpoint[i];
            ans+=g[1][x];
            tree[1][x]=1;//tree[x][y]为1表示在当前连通块中,否则不在
            tree[x][1]=1;
            m++;//连通块个数
        }
    }
    for(int i=m+1;i<=k;i++)//第三步第四部
    {
        memset(dp,-1,sizeof(dp));
        dp[1].z=-inf;
        for(int j=2;j<=cnt;j++)
        if(tree[1][j])
        dp[j].z=-inf;
        dfs(1,-1);//通过从起点dfs找到终点到各个节点路径上的权值最大的边,并将起点存在dp[i].x,终点存在dp[i].y,边权dp[i].z

		//对于除了终点的所有节点j,如果g[1][j]-dp[j].z<0说明可以通过将e(dp[j].x,dp[j].y)替换为e(1,j)使得生成树权值和变小
        int idx,minmum=inf;
        for(int j=2;j<=cnt;j++)
        {
            if(minmum>g[1][j]-dp[j].z)
            {
                minmum=g[1][j]-dp[j].z;
                idx=j;
            }
        }
        if(minmum>0)//无法找到对应的j使得g[1][j]-dp[j].z<0,退出循环
        break;
        tree[idx][1]=tree[1][idx]=1;//将替换的边加入当前生成树中
        tree[dp[idx].x][dp[idx].y]=tree[dp[idx].y][dp[idx].x]=1;
        ans+=minmum;//更新答案
    }
}

void init()//初始化
{
    cnt=1;
    tot=0;
    ans=0;
    m=0;
    mp["Park"]=1;
    memset(g,inf,sizeof(g));
    memset(tree,0,sizeof(tree));
    memset(minedge,inf,sizeof(minedge));
    memset(linkpoint,0,sizeof(linkpoint));
    for(int i=1;i<=maxn;i++)
    fa[i]=i;
}
int main()
{
    cin>>n;
    string s1,s2;
    int t;
    init();
    for(int i=1;i<=n;i++)
    {
        cin>>s1>>s2>>t;
        if(!mp[s1])
        mp[s1]=++cnt;
        if(!mp[s2])
        mp[s2]=++cnt;
        int u=mp[s1];
        int v=mp[s2];
        edge[++tot].x=u;
        edge[tot].y=v;
        edge[tot].z=t;
        g[u][v]=g[v][u]=min(g[u][v],t);
    }
    cin>>k;
    Kruskal();
    parktoother();
    cout<<"Total miles driven: "<<ans<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值