[CH5102]Mobile Service

原题

题目描述:

一个公司有三个移动服务员,最初分别在位置1,2,3处。
如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。从 \(p\)\(q\) 移动一个员工,需要花费 \(c(p,q)\)。这个函数不一定对称,但保证 \(c(p,p)=0\)
给出N个请求,请求发生的位置分别为 \(p_1\) ~ \(p_N\)。公司必须按顺序依次满足所有请求,目标是最小化公司花费,请你帮忙计算这个最小花费。\(N≤1000\),位置是1~200的整数。

输入格式

第一行有两个整数 \(L,N(3\le L\le 200, 1\le N\le 1000)\)\(L\) 是位置数;\(N\) 是请求数。每个位置从 \(1\)\(L\) 编号。下 \(L\) 行每行包含 \(L\) 个非负整数。第 \(i+1\) 行的第 \(j\) 个数表示 \(c(i,j)\) ,并且它小于 \(2000\)。最后一行包含 \(N\) 个数,是请求列表。一开始三个服务员分别在位置 \(1,2,3\)

输出格式

一个数M,表示最小服务花费。

样例输入

5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1

样例输出

5

\(\text{Solution:}\)

我们很容易想到用一个四元组 \((i, x, y, z)\) 来表示一个状态,即已经处理好 \(i\) 个请求,三个服务员的位置是 \(x,y,z\) , 显然有转移:

\[\begin{aligned} F[i + 1,p_{i + 1]},y,z] &= min(F[i,x,y,z] + c[x,p_{i+1}])\\ &\dots \end{aligned} \]
然而这样的状态太多需要枚举 \(1000\times 200^3\) 次,所以我们想办法设计一种状态更少的能够覆盖整个状态空间的"维度集合"

仔细观察可以发现,在状态 \((i,x,y,z)\) 中,一定会有 \(x\)\(y\)\(z\) 中的一个等于 \(p_i\), 所以我们可以通过枚举 \(i\) 来确定两维状态,而剩下的两维状态就直接枚举,枚举规模降低到了 \(1000\times 200^2\)

所以用三元组 \((i, x, y)\) 表示处理好前i个请求,一个服务员位于 \(p_i\), 另外两个分别位于 \(x,z\)

\[\begin{aligned} F[i+1,x,y]&=min(F[i,x,y]+c[p_i,p_{i+1}])\\ F[i+1,p_i,y]&=min(F[i,x,y]+c[x,p_{i+1}])\\ F[i+1,x,p_i]&=min(F[i,x,y]+c[y,p_{i+1}]) \end{aligned} \]
第一个式子很好理解,第二个式子表示从 \((i,p_i,x,y)\ \to \ (i+1,p_i,p_{i+1},y)\), 想想状态的定义就知道为什么转移后 \(x\) 变成 \(p_i\) 了, 第三个式子同理。

\(\text{这题告诉我们:}\)

对于线性的 \(\text{dp}\) ,一般先枚举阶段再枚举状态最后枚举决策,枚举顺序一定不能混淆,但是在状态表示清晰的的时候往往需要枚举的维数也多从而导致复杂度过高,所以需要仔细观察题目所给的条件,考虑不同维度之间的状态是否能由其他维度的状态或阶段推出 ,从而减少枚举的维度。

#include <iostream>
#include <cstring>

int n, m;
int f[1020][202][202], c[202][202], p[1020];
void chkmin(int &a, int b) 
{ if (a > b) a = b; }

int main()
{

    std::cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= n; ++ j)
            std::cin >> c[i][j];
    for (int i = 1; i <= m; ++ i)
        std::cin >> p[i];

    memset(f, 0x3f, sizeof f);
    f[0][1][2] = 0; p[0] = 3;
    for (int i = 0; i < m; ++ i) 
        for (int j = 1; j <= n; ++ j)
            for (int l = 1; l <= n; ++ l)
            {
                if (f[i][j][l] == 0x3f3f3f3f) continue;
                if (p[i + 1] != j and p[i + 1] != l)
                    chkmin(f[i + 1][j][l], f[i][j][l] + c[p[i]][p[i + 1]]);
                if (p[i + 1] != l and p[i + 1] != p[i])
                    chkmin(f[i + 1][p[i]][l], f[i][j][l] + c[j][p[i + 1]]);
                if (p[i + 1] != j and p[i + 1] != p[i])
                    chkmin(f[i + 1][j][p[i]], f[i][j][l] + c[l][p[i + 1]]);
            }
    int ans = 0x3f3f3f3f;
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= n; ++ j)
            chkmin(ans, f[m][i][j]);
    std::cout << ans << std::endl;
}

转载于:https://www.cnblogs.com/cnyali-Tea/p/10499516.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值