题目

传送门

解法

比较容易想出一个 \(\mathtt{dp}\):令 \(dp_{i,j,k}\) 为前 \(i\) 天赢了 \(j\) 场,剩余容量为 \(k\)​ 的概率。转移方程就是:

\[dp_{i,j,k}=dp_{i-1,j,k}\cdot (1-p_i)+dp_{i-1,j-1,k-a_i}\cdot p_i \]

看似 \(k\) 这一维比较大,但是实际上我们可以将其限制在 \(n\) 的范围,也即 \(200\)​ 以内。因为只要容量到达了 \(200\) 就一定够减了,而且转移方程的系数与 \(k\) 无关。相当于最后统计 \(\sum_{i=l}^n\sum_{j=0}^{200}{dp_{n,i,j}}\)。这样就是 \(\mathcal O(n^3)\)

但是实际上这样转移是有一定问题的。我们可以先获得后面的容积,然后再得到前面的物品。不妨将容积扩大到 \([0,400]\),最后取剩余容量在 \([200,400]\)\(dp\) 值即可。

代码

#include <cstdio>
#include <iostream>
using namespace std;

int n, l, k, val[205];
double dp[205][205][405], p[205], ans;

int read() {
    int x = 0, f = 1; char s;
    while((s = getchar()) < '0' || s > '9') if(s == '-') f = -1;
    while(s >= '0' && s <= '9') {x = (x << 1) + (x << 3) + (s ^ 48); s = getchar();}
    return x * f;
}

int main() {
    n = read(), l = read(), k = read();
    for(int i = 1; i <= n; ++ i) scanf("%lf", &p[i]), p[i] /= 100;
    for(int i = 1; i <= n; ++ i) val[i] = read();
    dp[0][0][k + 200] = 1;
    for(int i = 0; i < n; ++ i)
        for(int j = 0; j <= i; ++ j)
            for(int v = 0; v <= 400; ++ v) {
                int s = min(400, v + val[i + 1]);
                dp[i + 1][j][v] += dp[i][j][v] * (1.0 - p[i + 1]);
                if(s >= 0) dp[i + 1][j + 1][s] += dp[i][j][v] * p[i + 1];
            }
    for(int i = l; i <= n; ++ i)
        for(int j = 200; j <= 400; ++ j)
            ans += dp[n][i][j];
    printf("%.10f\n", ans);
    return 0;
}
posted on 2020-04-09 11:50  Oxide  阅读(9)  评论(0编辑  收藏  举报