题目描述
一个公司有三个移动服务员。如果某个地方有一个请求,某个员工必须赶到那个地方去(那个地方没有其他员工),某一时刻只有一个员工能移动。被请求后,他才能移动,不允许在同样的位置出现两个员工。从 p 到
q 移动一个员工,需要花费 c(p,q) 。这个函数没有必要对称,但是 c(p,p)=0 。公司必须满足所有的请求。目标是最小化公司花费。
输入格式 1770.in
第一行有两个整数 L ,
N ( 3≤L≤200 , 1≤N≤1000 )。 L 是位置数 ;N 是请求数。每个位置从 1 到 L 编号。
下L 行每行包含 L 个非负整数。第i+1 行的第 j 个数表示c(i,j) ,并且它小于2000。
最后一行包含 N 个数,是请求列表。一开始三个服务员分别在位置1,2,3。
输出格式 1770.out
一个数
M ,表示最小服务花费。
输入样例 1770.in
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
输出样例 1770.out
5
这题不妨可以类比一下之前做过的多滋味的咖啡一题,也是一种类似的多维分配型 dp。(这个名词是我自己yy出来的,不知道是不是对的)
有的人可能反应不够灵活(比如我……),一看题不会想到 dp,会先写个贪心,然而只有 10 分。
实际上这题为什么能 dp 呢?我们不仅要知道一道题怎么做,更要明白为什么能够这么做。只有把握了题目、算法和数据结构之间的关系,才能更好解题。
其实很简单。因为请求是有序的,而且同一时刻还只有一个人在移动,显然具有阶段性,而且无后效性。
最直观的做法,可以用
f[i][A][B][C]
表示处理完请求i之后,三名服务员分别在位置
A
、
转移也不是很难,设第
f[i][A][x][C]=min(f[i−1][A][B][C]+cost[B][x])
f[i][x][B][C]=min(f[i−1][A][B][C]+cost[A][x])
很遗憾这样的做法既会 MLE 又会 TLE。那么必然要把状态改进一下。
我们结合题意,再观察一下上面的状态转移方程,可以发现,处理完请求
i
之后,三个服务员中必定有一人在
那么,我们为什么要多花一维状态记他呢??
直接可以记
f[i][A][B]
,表示处理完请求
i
之后,三名服务员(无序)分别在位置
设第
i−1
次请求位置为
y
,于是状态转移方程就变成了:
f[i][B][y]=min(dp[i−1][B][C]+c[C][x])
f[i][C][y]=min(dp[i−1][B][C]+c[B][x])
转移的时候注意,其中
A
、
这样交上去几乎就是一个正解,很可惜却只有 95 分。
再来改进。既然
最后总结一下这题,其实做 dp 题最重要的还是如何划分阶段,恰当的描述状态,并进行合理的状态优化。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int maxl = 200 + 10;
const int maxn = 1e3 + 10;
int l, n;
int c[maxl][maxl], ask[maxn];
int dp[2][maxl][maxl];
int main(void) {
freopen("1770.in", "r", stdin);
freopen("1770.out", "w", stdout);
scanf("%d%d", &l, &n);
for (int i = 1; i <= l; i++)
for (int j = 1; j <= l; j++)
scanf("%d", &c[i][j]);
for (int i = 1; i <= n; i++) scanf("%d", &ask[i]);
memset(dp, 0x7f, sizeof dp);
//初始化状态
dp[1][1][2] = dp[1][2][1] = c[3][ask[1]];
dp[1][1][3] = dp[1][3][1] = c[2][ask[1]];
dp[1][2][3] = dp[1][3][2] = c[1][ask[1]];
for (int i = 2; i <= n; i++) {
memset(dp[i & 1], 0x7f, sizeof dp[i & 1]); //务必清空,否则可能因为前面的值而造成影响
for (int j = 1; j <= l; j++)
if (j != ask[i - 1]) //检查限制,下同
for (int k = 1; k <= l; k++)
if (k != j && k != ask[i - 1]) {
//printf("%d %d %d %d\n", i, j, k, dp[(i - 1) & 1][j][k]);
dp[i & 1][j][k] = dp[i & 1][k][j] = min(dp[i & 1][j][k] , dp[(i - 1) & 1][j][k] + c[ask[i - 1]][ask[i]]);
dp[i & 1][j][ask[i - 1]] = dp[i & 1][ask[i - 1]][j] = min(dp[i & 1][j][ask[i - 1]], dp[(i - 1) & 1][j][k] + c[k][ask[i]]);
dp[i & 1][k][ask[i - 1]] = dp[i & 1][ask[i - 1]][k] = min(dp[i & 1][k][ask[i - 1]], dp[(i - 1) & 1][j][k] + c[j][ask[i]]);
//因为无序,所以j和k实际上是有对称性的
}
}
int ans = 0x7fffffff;
for (int i = 1; i <= l; i++)
if (i != ask[n])
for (int j = 1; j <= l; j++)
if (i != j && j != ask[n]) ans = min(ans, dp[n & 1][i][j]);
printf("%d\n", ans);
return 0;
}