题目描述:
一个公司有三个移动服务员,最初分别在位置1,2,3处。
如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。从 (p) 到 (q) 移动一个员工,需要花费 (c(p,q))。这个函数不一定对称,但保证 (c(p,p)=0)。
给出N个请求,请求发生的位置分别为 (p_1) ~ (p_N)。公司必须按顺序依次满足所有请求,目标是最小化公司花费,请你帮忙计算这个最小花费。(N≤1000),位置是1~200的整数。
输入格式
第一行有两个整数 (L,N(3le Lle 200, 1le Nle 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
( ext{Solution:})
我们很容易想到用一个四元组 ((i, x, y, z)) 来表示一个状态,即已经处理好 (i) 个请求,三个服务员的位置是 (x,y,z) , 显然有转移:
然而这样的状态太多需要枚举 (1000 imes 200^3) 次,所以我们想办法设计一种状态更少的能够覆盖整个状态空间的"维度集合"
仔细观察可以发现,在状态 ((i,x,y,z)) 中,一定会有 (x) 或 (y) 或 (z) 中的一个等于 (p_i), 所以我们可以通过枚举 (i) 来确定两维状态,而剩下的两维状态就直接枚举,枚举规模降低到了 (1000 imes 200^2) 。
所以用三元组 ((i, x, y)) 表示处理好前i个请求,一个服务员位于 (p_i), 另外两个分别位于 (x,z) 。
第一个式子很好理解,第二个式子表示从 ((i,p_i,x,y) o (i+1,p_i,p_{i+1},y)), 想想状态的定义就知道为什么转移后 (x) 变成 (p_i) 了, 第三个式子同理。
( ext{这题告诉我们:})
对于线性的 ( ext{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;
}