算法
(DP,线性DP) $O(NL^2)$
状态表示:
f[i][x][y]
表示已经处理完前i
个请求,且三个服务员分别在p[i], x, y
的所有方案的集合;f[i][x][y]
的值是集合中所有方案的花费的最小值;
状态计算:
这里状态之间的拓扑关系比较特殊,f[i][x][y]
所依赖的状态枚举起来不太方便,但f[i][x][y]
被依赖的很容易枚举,只有3类:
- 位于
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]
取最小值即可。
时间复杂度
共有 $NL^2$ 个状态,计算每个状态需要 $O(1)$ 的计算量,因此总时间复杂度是 $O(NL^2)$。
C++ 代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
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;
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;
}
为什么x去往p[i+1] 得到的转移后的状态是f[i+1][p[i]][y]而不是f[i+1][p[i+1]][y] 呢 不是去往了p[i+1]吗
移动后点的位置是不计的 我们只记录不变的 因为变化的那个点的位置一定在p[i+1]
因为f[i][x][y]表示完成前i个请求,“另外两个”员工在x, y
very good!
初始化怎么初始的
三维dp 用第一维代表处理完第i个请求 又代表第一个服务员在p[i]的位置 这样转移的时候不会矛盾吗 比如x服务员转移到时候 f[i+1][x][y]中的i+1 表示第一个服务员在p[i+1]的位置吗
不会,因为一定有一个快递员处理了第 i+1 个请求。
P[0]= 3是什么意思
第0个任务在 1或者2或者3 这里p[0]可以等于1或者2或者3
不对哇,但是这里p[0]=1就错了
肯定不对啊 只改p不改f肯定就是错的 p[0] = 3表示第一个人的位置初始为3 如果你改成p[0] = 1那f的起始状态就要改 改成f[0][2][3]
状态是不是少算了一些,如果当前已经有员工处于所需位置,那其他员工应该可以随意移动一次。
您上面的代码对于以下算例输出为9,一共有五个位置,其中最后一个位置和其他位置之间的距离很近,而前四个位置之间很远,请求为[1, 2, 3, 4],那么最优的方案是不是由一个员工先绕到5再到4,答案为2呢?
5 4
0 9 9 9 1
9 0 9 9 1
9 9 0 9 1
9 9 9 0 1
1 1 1 1 0
1 2 3 4
从《算法竞赛进阶指南》中的题解来看,本题中少了个条件,题目描述已更新,增加了限制:过程中不能去其他额外的位置。
老师,请问为什么仅需要判断if(a==b||b==c||a==c)就可以?不去特判if(a==q[i])吗?
如果一个状态不合法,那么就会在
if (x == y || x == z || y == z) continue;
这里被过滤掉了,也就是不合法的状态不会用来更新其他状态,且在统计答案的时候也会被过滤掉。平时自己写的时候为了清楚一些,建议把
if (a == q[i + 1])
也加上。谢谢老师!!!
状态转移的时候,有重复
如 f[i][a][b] 和 f[i][b][a],
应该可以优化成
这里可以优化,但循环的时候只限制a < b还不够,还需要在更新的时候确保第二维小于第三维。
嗯嗯