题意
题解
F l o y d Floyd Floyd 求解任意两间教室间的最短路。至多申请 m m m 次机会看做背包问题,则可以使用 D P DP DP 求解。
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 代表从时间 i i i 开始,还可以申请更换 j j j 次, 位于房间 p o s [ i ] [ k ] pos[i][k] pos[i][k],到时间 n n n 的终止状态耗费体力值的最小数学期望。 k k k 为 0 , 1 0,1 0,1 时, p o s [ i ] [ k ] pos[i][k] pos[i][k] 分别代表 c i , d i c_i,d_i ci,di。任一状态的下一状态都面临两种决策,即下一个时间点是否申请更换教室。设 p o s [ i − 1 ] [ k ] , p o s [ i ] [ k ′ ] pos[i-1][k],pos[i][k'] pos[i−1][k],pos[i][k′] 间最短距离为 d s [ k ] [ k ′ ] ds[k][k'] ds[k][k′],则有递推 d p [ i ] [ j ] [ k ] = min ( d p [ i + 1 ] [ j ] [ 0 ] + d s [ k ] [ 0 ] , p i ( d p [ i + 1 ] [ j − 1 ] [ 1 ] + d s [ k ] [ 1 ] ) + ( 1 − p i ) ( d p [ i + 1 ] [ j − 1 ] [ 0 ] + d s [ k ] [ 0 ] ) dp[i][j][k]=\min (dp[i+1][j][0]+ds[k][0],p_i(dp[i+1][j-1][1]+ds[k][1])+(1-p_i)(dp[i+1][j-1][0]+ds[k][0]) dp[i][j][k]=min(dp[i+1][j][0]+ds[k][0],pi(dp[i+1][j−1][1]+ds[k][1])+(1−pi)(dp[i+1][j−1][0]+ds[k][0]) 设节点 0 0 0 为到任意教室距离为 0 0 0 的节点,它位于时间 0 0 0 处,则答案为 d p [ 0 ] [ m ] [ 0 ] dp[0][m][0] dp[0][m][0]。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2005, maxm = 2005, maxv = 305;
int N, M, V, E, pos[maxn][2], ds[maxv][maxv];
double P[maxn], dp[maxn][maxm][2];
int main()
{
scanf("%d%d%d%d", &N, &M, &V, &E);
for (int i = 1; i <= N; ++i)
scanf("%d", &pos[i][0]);
for (int i = 1; i <= N; ++i)
scanf("%d", &pos[i][1]);
for (int i = 1; i <= N; ++i)
scanf("%lf", P + i);
for (int i = 1; i <= V; ++i)
for (int j = i + 1; j <= V; ++j)
ds[i][j] = ds[j][i] = inf;
for (int i = 1, x, y, z; i <= E; ++i)
scanf("%d%d%d", &x, &y, &z), ds[x][y] = ds[y][x] = min(ds[x][y], z);
for (int k = 1; k <= V; ++k)
for (int i = 1; i <= V; ++i)
for (int j = 1; j <= V; ++j)
ds[i][j] = min(ds[i][j], ds[i][k] + ds[k][j]);
for (int i = N - 1; i >= 0; --i)
for (int j = 0; j <= M; ++j)
for (int k = 0; k <= 1; ++k)
{
dp[i][j][k] = dp[i + 1][j][0] + ds[pos[i][k]][pos[i + 1][0]];
if (!j)
continue;
double x = dp[i + 1][j - 1][1] + ds[pos[i][k]][pos[i + 1][1]];
double y = dp[i + 1][j - 1][0] + ds[pos[i][k]][pos[i + 1][0]];
dp[i][j][k] = min(dp[i][j][k], P[i + 1] * x + (1 - P[i + 1]) * y);
}
printf("%.2f\n", dp[0][M][0]);
return 0;
}
然而,上述 D P DP DP 方法是错误的。可以发现并证明其结果小于等于标准答案。原因在于,选择申请教室时依概率有两个状态转移的方向,以教室作为 D P DP DP 的第三维,可能导致 c i , d i c_i,d_i ci,di 的前一步最优策略是不一致的,此时得到的是一个非法的解。
那么为了保证最优解的方案中,每个时间点选择的策略是唯一的,将 D P DP DP 的第三维取为当前选择的策略,即是否选择更换教室。 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 代表从时间 [ 1 , i ] [1,i] [1,i] 内至多申请 j j j 次,当前时间点是否申请的状态下, 耗费体力值的最小数学期望。 k = 0 k=0 k=0 时,依概率 1 1 1 当前处于 c i c_i ci; k = 1 k=1 k=1 时,依概率 p i p_i pi 位于 d i d_i di,依概率 1 − p i 1-p_i 1−pi 位于 c i c_i ci。与前述 D P DP DP 同理,进行对应的状态转移即可。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2005, maxm = 2005, maxv = 305;
int N, M, V, E, C[maxn], D[maxn];
int ds[maxv][maxv];
double P[maxn], dp[maxn][maxm][2];
int main()
{
scanf("%d%d%d%d", &N, &M, &V, &E);
for (int i = 1; i <= N; ++i)
scanf("%d", C + i);
for (int i = 1; i <= N; ++i)
scanf("%d", D + i);
for (int i = 1; i <= N; ++i)
scanf("%lf", P + i);
for (int i = 1; i <= V; ++i)
for (int j = i + 1; j <= V; ++j)
ds[i][j] = ds[j][i] = inf;
for (int i = 1, x, y, z; i <= E; ++i)
scanf("%d%d%d", &x, &y, &z), ds[x][y] = ds[y][x] = min(ds[x][y], z);
for (int k = 1; k <= V; ++k)
for (int i = 1; i <= V; ++i)
for (int j = 1; j <= V; ++j)
ds[i][j] = min(ds[i][j], ds[i][k] + ds[k][j]);
dp[1][0][1] = inf;
for (int i = 2; i <= N; ++i)
{
dp[i][0][0] = dp[i - 1][0][0] + ds[C[i - 1]][C[i]], dp[i][0][1] = inf;
for (int j = 1; j <= M; ++j)
{
int a = ds[C[i - 1]][C[i]], b = ds[C[i - 1]][D[i]];
int c = ds[D[i - 1]][C[i]], d = ds[D[i - 1]][D[i]];
dp[i][j][0] = min(dp[i - 1][j][0] + a, dp[i - 1][j][1] + P[i - 1] * c + (1 - P[i - 1]) * a);
double x = (1 - P[i - 1]) * (1 - P[i]), y = (1 - P[i - 1]) * P[i];
double z = P[i - 1] * (1 - P[i]), w = P[i - 1] * P[i];
dp[i][j][1] = min(dp[i - 1][j - 1][0] + P[i] * b + (1 - P[i]) * a, dp[i - 1][j - 1][1] + x * a + y * b + z * c + w * d);
}
}
printf("%.2f\n", min(dp[N][M][0], dp[N][M][1]));
return 0;
}