274. 移动服务(线性dp)

分析:L个位置,N个请求,让我们求最小花费

影响花费因素:处理花费的服务员所在的位置

限制条件

  • 过程中不能去其他的位置,只能去发生请求的位置
  • 不允许两个员工在同一个位置

划分依据:由于有3个服务员,对于每个请求来说,有三种情况

  • 服务员1去服务
  • 服务员2去服务
  • 服务员3去服务

本题的数据范围是200,对于难做的问题,可以思考暴力枚举怎么做,然后再对枚举进行优化

枚举

有N个请求,3种选择,分别枚举,有3^N种方案

动态规划

有N个请求,每个请求时x,y,z的位置范围为[3,200]

而每个请求让x,y,z去做带来的花费影响是来自于 x,y,z此时的位置

枚举会记住一个位置内的所有方案,而用dp算法,我们可以贪心一下,只保存这个位置内最小的花费总和,这样子不会有后效性,能够得到全局最优解

状态表示:dp[i][x][y][z]

含义:处理了前i个选择,此时服务员1在x,服务员2在y,服务员3在z的最小花费s

集合:所有处理了前i个选择,此时服务员1在x,服务员2在y,服务员3在z的集合

属性:min

状态计算:本题根据拓扑排序,可以分为3种方向递推,u=p[i+1]

  • 分给x做        dp[i+1][u][y][z]=min(dp[i+1][u][y][z],v+c[x][u]);
  • 分给y做        dp[i+1][x][u][z]=min(dp[i+1][x][u][z],v+c[y][u]);
  • 分给z做        dp[i+1][x][y][u]=min(dp[i+1][x][y][u],v+c[z][u]);

初始化:全为正无穷  dp[0][1][2][3]=0;

for(int i=0;i<n;i++)
        for(int x=1;x<=l;x++)
            for(int y=1;y<=l;y++)
                for(int z=1;z<=l;z++)
                {
                    int v=dp[i][x][y][z];
                    if(x==y||x==z||y==z)
                        continue;
                    //由于是正拓扑排序,所以i时候是做第i+1个选择
                    int u=p[i+1];
                    //分给x做
                    dp[i+1][u][y][z]=min(dp[i+1][u][y][z],v+c[x][u]);
                    //分给y做
                    dp[i+1][x][u][z]=min(dp[i+1][x][u][z],v+c[y][u]);
                    //分给z做
                    dp[i+1][x][y][u]=min(dp[i+1][x][y][u],v+c[z][u]);
                }

优化

以上的方法空间时间复杂度均为O(n l^3),毫无疑问太高了,思考有没有可以降维的方法

猜测,如果能够减掉一维,只枚举两个服务员位置,另一个服务员和这两个服务员竞争上岗

状态计算:
这里状态之间的拓扑关系比较特殊,f[i][x][y]所依赖的状态枚举起来不太方便,但f[i][x][y]被依赖的很容易枚举,只有3类:

  • 位于p[i]的服务员出发前往p[i + 1],此时状态变成f[i + 1][x][y] = f[i][x][y] + w[p[i]][p[i + 1]];
  • 位于x的服务员出发前往p[i + 1],此时状态变成f[i + 1][p[i]][y] = f[i][x][y] + w[x][p[i + 1]];
  • 位于y的服务员出发前往p[i + 1],此时状态变成f[i + 1][x][p[i]] = f[i][x][y] + w[y][p[i + 1]];

证明:

不同服务员在同一位置去往另外一个位置的消费都是一样的,这里相当于把z和x调换了一下位置,其实还是可以看作都是z去做的,由于x,y,z的坐标都只会在初始点和请求集合之中,所以z的坐标只会是1,2,3和p[i]

完整代码

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1005;
const int M=205;
int c[M][M];
int p[N];
int dp[N][M][M];

int main()
{
    int l,n;
    cin>>l>>n;
    for(int i=1;i<=l;i++)
        for(int j=1;j<=l;j++)
            cin>>c[i][j];
    for(int i=1;i<=n;i++)
        cin>>p[i];
    memset(dp,0x3f,sizeof dp);
    dp[0][1][2]=0;
    p[0]=3;
    for(int i=0;i<n;i++)
        for(int x=1;x<=l;x++)
            for(int y=1;y<=l;y++)
            {
                int z=p[i];
                if(x==y||y==z||x==z) continue;
                int v=dp[i][x][y];
                int u=p[i+1];
                //派x去做
                dp[i+1][p[i]][y]=min(dp[i+1][p[i]][y],v+c[x][u]);
                //派y去做
                dp[i+1][x][p[i]]=min(dp[i+1][x][p[i]],v+c[y][u]);
                //派z去做
                dp[i+1][x][y]=min(dp[i+1][x][y],v+c[z][u]);
            }
    int res=0x3f3f3f3f;
    for(int x=1;x<=l;x++)
        for(int y=1;y<=l;y++)
            res=min(res,dp[n][x][y]);
    cout<<res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值