[HNOI2015]亚瑟王

可以发现如果我们要模拟每一轮的过程将会完全不可做,因此我们可以考虑使用期望的线性性,合理地将答案划分成很多好求的部分,然后再简单相加,例如在本题当中,我们可以考虑每张卡牌对答案的贡献,于是有 (Ans = sum P_i imes d_i)(P_i) 为该卡牌触发的概率。于是问题在于如何将 (P_i) 算出来。

可以发现难处理的地方实际是如果前面有卡牌触发那么后面都都将停止,先从最简单的情况出发,考虑第一张卡牌,因为它前面没有任何卡牌,可以发现它触发的概率是一个经典的正难则反问题,即 (1 - (1 - p_i) ^ r)。再考虑之后的每一张牌,我们发现只需要知道最终状态下有几次能经过它,令这个经过次数为 (x),那么类似地我们可以算出触发的概率 (1 - (1 - p_i) ^ x),并且我们可以发现,这个次数 (x) 实际上就是 (r -)(i) 之前触发过卡牌的次数。因此我们只需要知道知道这个次数的概率即可,于是我们可以令 (dp_{i, j}) 为在最终状态下,前 (i) 张卡牌触发了 (j) 张的概率。于是我们有转移

[dp_{i, j} = (1 - (1 - p_i) ^ {r - j + 1})dp_{i - 1, j - 1} + (1 - p_i) ^ {r - j}dp_{i - 1, j} ]

那么最终的 (P_i = sumlimits_{j = 0} ^ {min{n, r}} dp_{i - 1, j} imes (1 - (1 - p_i) ^ {r - j}))。最后根据概率的线性性直接算 (Ans = sum P_i imes d_i) 即可。

#include<bits/stdc++.h>
using namespace std;
#define N 220 + 5
#define rep(i, l, r) for(int i = l; i <= r; ++i)
int T, n, r, d[N];
double ans, p[N], g[N], P[N][N], dp[N][N];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int main(){
    T = read();
    while(T--){
        n = read(), r = read(), ans = 0;
        rep(i, 1, n) scanf("%lf", &p[i]), d[i] = read();
        memset(g, 0, sizeof(g)), memset(P, 0, sizeof(P)), memset(dp, 0, sizeof(dp));
        rep(i, 1, n){
            P[i][0] = 1;
            rep(j, 1, r) P[i][j] = P[i][j - 1] * (1.0 - p[i]);
        }
        dp[0][0] = 1.0;
        rep(i, 1, n) rep(j, 0, min(r, n)){
            if(j >= 1) dp[i][j] = dp[i - 1][j - 1] * (1.0 - P[i][r - j + 1]);
            dp[i][j] += dp[i - 1][j] * P[i][r - j];
        }
        rep(i, 1, n) rep(j, 0, min(r, n)) g[i] += dp[i - 1][j] * (1.0 - P[i][r - j]);
        rep(i, 1, n) ans += 1.0 * g[i] * d[i];
        printf("%.10f
", ans);
    }
    return 0;
}
GO!
原文地址:https://www.cnblogs.com/Go7338395/p/13442537.html