移动服务(算法竞赛进阶指南)
一个公司有三个移动服务员,最初分别在位置 1,2,3 处。
如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。
某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。
从 p 到 q 移动一个员工,需要花费 c(p,q)。
这个函数不一定对称,但保证 c(p,p)=0。
给出 N 个请求,请求发生的位置分别为 p1∼pN。
公司必须按顺序依次满足所有请求,且过程中不能去其他额外的位置,目标是最小化公司花费,请你帮忙计算这个最小花费。
输入格式
第 1 行有两个整数 L,N,其中 L 是位置数量,N 是请求数量,每个位置从 1 到 L 编号。
第 2 至 L+1 行每行包含 L 个非负整数,第 i+1 行的第 j 个数表示 c(i,j),并且它小于 2000。
最后一行包含 N 个整数,是请求列表。
一开始三个服务员分别在位置 1,2,3。
输出格式
输出一个整数 M,表示最小花费。
数据范围
3≤L≤200,
1≤N≤1000
输入样例:
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
这道题我们首先想到的几个元素是:第几个要求,为了转移状态我们需要知道服务员的位置,所以还需要三个服务员的位置x,y,z。
状态确立:f[i][x][y][z]表示第i个要求,三个服务员位置分别在x,y,z上的最小值。
不妨写一下转态如何转移:
f[i + 1][x][y][p[i + 1]] = f[i][x][y][z] + w[z][p[i + 1]](因为我们没有动x,y。根据题目要求:
某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。
公司必须按顺序依次满足所有请求(所以这里的z一定是p[i])
所以又写成
f[i + 1][x][y][p[i + 1]] = f[i][x][y][p[i]] + w[p[i]][p[i + 1]].
别的可能:
f[i + 1][p[i + 1]][y][z] = f[i][p[i]][y][z] + w[x][p[i + 1]]
f[i + 1][x][p[i + 1]][z] = f[i][x][p[i]][z]. + w[y][p[i + 1]]
观察这三个式子发现有一个共同点:p[i],且p[i]可以用f[i][][][]中的i表示出来,所以消去这一维。
所以:
f[i + 1][x][y] = f[i][x][y] + w[p[i]][p[i + 1]].
f[i + 1][y][z] = f[i][y][z] + w[x][p[i + 1]]
f[i + 1][x][z] = f[i][x][z]. + w[y][p[i + 1]]
从整个过程考虑,x,y,z没有区别,性质是相同的,字符不同,但他们表示的意义相同.
我们重看消维的过程,本质是用i来间接代替了一维,间接代替了一个服务员的位置。所以我们只需要存两个服务员的位置就可以了。
转态表示:f[i][x][y]表示已经处理完前i个请求,且三个服务员分别在p[i], x, y的所有方案的集合;
f[i][x][y]的值是集合中所有方案的花费的最小值;
状态转移:位于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]];
答案从f[m][1...n][1...n]取最小值。
#include<bits/stdc++.h>
using namespace std;
const int N = 210, M = 1010, INF = 0x3f3f3f3f;
int n, m;
int w[N][N];
int f[M][N][N];
int p[M];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
scanf("%d", &w[i][j]);
for (int i = 1; i <= m; i ++ ) scanf("%d", &p[i]);
p[0] = 3;
memset(f, 0x3f, sizeof f);
f[0][1][2] = 0,f[0][2][3] = 0,f[0][1][3] = 0;
//只初始化f[0][1][2] = 0也能过,可以理解成第0个要求是一个服务员站在第三个位置上
for (int i = 0; i < m; i ++ )
for (int x = 1; x <= n; x ++ )
for (int y = 1; y <= n; y ++ )
{
int z = p[i], v = f[i][x][y];
if (x == y || x == z || y == z) continue;//题目要求两个人不能站到一起
int u = p[i + 1];
f[i + 1][x][y] = min(f[i + 1][x][y], v + w[z][u]);
f[i + 1][z][y] = min(f[i + 1][z][y], v + w[x][u]);
f[i + 1][x][z] = min(f[i + 1][x][z], v + w[y][u]);
}
int res = INF;
for (int x = 1; x <= n; x ++ )
for (int y = 1; y <= n; y ++ )
{
int z = p[m];
if (x == y || x == z || y == z) continue;
res = min(res, f[m][x][y]);
}
printf("%d\n", res);
return 0;
}