P1850 [NOIP2016 提高组] Floyd + 数学期望 + DP

164 篇文章 0 订阅
159 篇文章 1 订阅
题意

传送门 P1850 [NOIP2016 提高组] 换教室

题解

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[i1][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][j1][1]+ds[k][1])+(1pi)(dp[i+1][j1][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 1pi 位于 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值