洛谷题单指南-概率与统计-P1850 [NOIP 2016 提高组] 换教室

原题链接:https://www.luogu.com.cn/problem/P1850

题意解读:n个时段,每个时段i有一门课程,同时在ci(默认)、di两个地点上课,地点之间转换要耗费体力,且能选择最多m个时段课程进行地点更换(c换d),每个时段更换成功概率为pi,求上完所有课耗费最小体力值的期望。

解题思路:

1、暴搜

直观上,可以先在n个时段中搜索所有m个时段,再枚举每个时段不换、换成功、换失败三种状态,计算每种状态下,地点之间切换的体力值期望,更新最小值。总体复杂度是O(2^n * 2^m * n)。

2、DP

由于到一个地点的体力值只与上一个地点有关,已经耗费的体力不影响后续的体力耗费,可以通过动态规划来解决。

  • 状态表示:

设f[i][j][k]表示前i个时段,申请更换j个地点,第i个课程的申请状态为k所耗费的最小体力值的期望;

特别的,k=0表示第i个课程不换,k=1表示第i个课程换(可能换成功,也可能失败)。

设s[i][j]表示从教室i到教室j的最小体力值,也就是最短路径,可以通过Floyd进行预处理。

  • 状态转移:

f[i][j][0] = min(

      f[i-1][j][0] + s[c[i-1]][c[i]],  //上一个教室没换

      f[i-1][j][1] + s[d[i-1]][c[i]] * p[i-1] + s[c[i-1]][c[i]] * (1-p[i-1])   //上一个教室换了,可能换成功、也可能没成功

      )

注意,j=0时,下面的式子无意义。

f[i][j][1] = min(

      f[i-1][j-1][0] + s[c[i-1]][c[i]] * (1-p[i]) + s[c[i-1]][d[i]] * p[i], //上一个教室没换,当前教室换了,可能成功或不成功

      f[i-1][j-1][1] + s[c[i-1]][c[i]] * (1-p[i-1]) * (1-p[i]) + s[c[i-1]][d[i]] * (1-p[i-1]) * p[i] + s[d[i-1]][c[i]] * p[i-1] * (1-p[i]) + s[d[i-1]][d[i]] * p[i-1] * p[i] //上一个教室换了、当前教室换了,有四种组合

      )

  • 初始化:

需要注意floyd的初始化,数组INF不能太大,避免加法溢出,1e9合适

f的初始化,初始为INF,第一个时段没有消耗体力,f[1][0][0] =f[1][1][1] =0

  • 答案:

所有的f[n][0]~f[n][m],取最小值。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 2005, V = 305, INF = 1e9;

int n, m, v, e;
int s[V][V];
double f[N][N][2];
int c[N], d[N];
double p[N];

void floyd()
{
    for(int k = 1; k <= v; k++)
        for(int i = 1; i <= v; i++)
            for(int j = 1; j <= v; j++)
                s[i][j] = min(s[i][j], s[i][k] + s[k][j]);
}

void dp()
{
    for(int i = 0; i <= n; i++)
        for(int j = 0; j <= m; j++)
            f[i][j][0] = f[i][j][1] = LLONG_MAX;
    f[1][0][0] = f[1][1][1] = 0;
    for(int i = 2; i <= n; i++)
    {
        for(int j = 0; j <= min(m, i); j++)
        {
            f[i][j][0] = min(
                            f[i-1][j][0] + s[c[i-1]][c[i]], 
                            f[i-1][j][1] + s[d[i-1]][c[i]] * p[i-1] + s[c[i-1]][c[i]] * (1 - p[i-1])
                            );
            if(j != 0)
                f[i][j][1] = min(
                            f[i-1][j-1][0] + s[c[i-1]][c[i]] * (1 - p[i]) + s[c[i-1]][d[i]] * p[i], 
                            f[i-1][j-1][1] + s[c[i-1]][c[i]] * (1 - p[i-1]) * (1 - p[i]) + s[c[i-1]][d[i]] * (1 - p[i-1]) * p[i] + s[d[i-1]][c[i]] * p[i-1] * (1 - p[i]) + s[d[i-1]][d[i]] * p[i-1] * p[i]
                            );
        }
    }
}

int main()
{
    cin >> n >> m >> v >> e;
    for(int i = 1; i <= n; i++) cin >> c[i];
    for(int i = 1; i <= n; i++) cin >> d[i];
    for(int i = 1; i <= n; i++) cin >> p[i];
    for(int i = 1; i <= v; i++)
    {
        for(int j = 1; j <= v; j++)
        {
            if(i == j) s[i][j] = 0;
            else s[i][j] = INF;
        }
    }
    for(int i = 1; i <= e; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        s[a][b] = s[b][a] = min(s[a][b], w);
    }
    floyd();
    dp();
    double ans = LLONG_MAX;
    for(int i = 0; i <= m; i++) ans = min(ans, min(f[n][i][0], f[n][i][1]));
    printf("%.2lf", ans);
}

 

posted @ 2026-01-14 16:45  hackerchef  阅读(1)  评论(0)    收藏  举报