Mobile Service
题面
一个公司有三个移动服务员,最初分别在位置 1,2,3 处。
如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。
某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。
从 p 到 q 移动一个员工,需要花费 c(p,q)。
这个函数不一定对称,但保证 c(p,p)=0。
给出 N 个请求,请求发生的位置分别为 p1∼pN。
公司必须按顺序依次满足所有请求,且过程中不能去其他额外的位置,目标是最小化公司花费,请你帮忙计算这个最小花费。
\(3 \le L \le 200\)
\(1 \le N \le 1000\)
题解
因为公司必须按顺序满足所有请求,所以我们可以按照这些请求划分阶段,设 \(f(i,p1,p2,p3)\) 表示处理完前 \(i\) 个请求,三个人分别在 \(p1,p2,p3\) 的最小代价
如果这样 \(dp\) ,那么状态数是 \(O(NL^3)\) 级别的,是不可接受的
仔细观察发现,这三个位置中一定有一个位置在第 \(i\) 个请求的位置,所以我们可以只记两个位置,\(f(i,p1,p2)\) 表示完成前 \(i\) 个请求,有一个人在 \(i\) 请求的位置,另外两个人在 \(p1,p2\) 的最小代价,这样的状态数就是 \(O(NL^2)\) 的,大概 4e7 ,可以接受
转移
这道题给我们两点启发:
-
求解线性dp问题,一般先确定阶段,若阶段不足以表示一个状态,则可以把所需的附加信息也作为状态的维度
在转移时,若总是从一个阶段转移到下一个阶段(本题 i -> i+1),则没有必要关心附加信息维度的大小变化情况(本题 x,y 在转移前后大小不定),因为无后效性已由阶段保证
-
在确定 dp 状态时,要选择最小的能够覆盖整个状态空间的 “维度集合”
若 dp 状态由多个维度构成,则应检查维度之间能否相互导出,排除冗余维度
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>using namespace std;const int N = 1010, M = 210;int n, m;
int p[N], c[M][M];
int f[N][M][M];int main () {cin >> m >> n;for (int i = 1; i <= m; i++) {for (int j = 1; j <= m; j++) {cin >> c[i][j];}}for (int i = 1; i <= n; i++) {cin >> p[i];}memset (f, 0x3f, sizeof f);f[0][1][2] = 0;//自己的初始化要符合定义p[0] = 3;for (int i = 0; i <= n; i++) {for (int j = 1; j <= m; j++) {for (int k = 1; k <= m; k++) {int x = p[i];//注意判断不合法状态if (x == j || x == k || j == k) continue;//可以在合适的地方用变量简化代码int v = f[i][j][k];f[i + 1][j][k] = min (f[i + 1][j][k], v + c[x][p[i + 1]]);f[i + 1][j][x] = min (f[i + 1][j][x], v + c[k][p[i + 1]]);f[i + 1][x][k] = min (f[i + 1][x][k], v + c[j][p[i + 1]]);}}}int ans = 2e9;for (int i = 1; i <= m; i++) {for (int j = 1; j <= m; j++) {ans = min (ans, f[n][i][j]);}}cout << ans << endl;return 0;
}