[BZOJ4008][HNOI2015]亚瑟王(概率DP)

本文介绍了一种计算多张卡牌在限定轮数内至少发动一次技能概率的方法,并通过动态规划实现。针对每张卡牌,计算其在所有轮次中至少被发动一次的概率,最终得出整体期望值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果能够求出每张卡牌在所有r轮中被发动的概率g[],那么答案显然为:
ni=1g[i]d[i]
第一步推出,g[1]=1(1p[1])r
再考虑第二张:
情况一:如果第1张牌没有发动过技能,那么第2张牌发动技能的概率为1(1p[2])r
情况二:如果第1张牌发动过1次技能,那么在第1张牌发动技能的那一轮,第2张牌绝对不会再发动技能了,因此第2张牌发动技能的概率为1(1p[2])r1
结合这个例子,可以得到,对于任意的i>1,在第1张牌到第i1张牌在所有r轮内是否发动技能已经确定的情况下,第i张牌被发动技能的概率只取决于第1张牌到第i1张牌中有多少张发动了技能。即如果有j张发动了技能,那么在此情况下第i张牌发动技能的概率为1(1p[i])rj
根据这个性质,就能想到一个DP模型:
f[i][j]表示前i张牌中,恰好有j张在所有r轮中被发动过的概率。
转移就比较好想了。分第i张牌发动与不发动两种情况:
1:发动。那么前i1张牌一定有j1张牌被发动技能,因此对于第i张牌,在r轮中有j1轮已经不会再发动技能了。所以:
f[i][j]+=(1(1p[i])rj+1)f[i1][j1]
2:不发动。那么前i1张牌中一定有j张牌被发动技能,因此对于第i张牌,在r轮中有j轮是绝对不会再发动技能的。所以:
f[i][j]+=(1p[i])rjf[i1][j]
因此,完整的转移方程为:
f[i][j]=((1(1p[i])rj+1)f[i1][j1])[j>0]
+((1p[i])rjf[i1][j])[ij]
那么求g就更容易了:
g[i]=min(i1,r)j=0(1(1p[i])rj)f[i1][j]
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 234, R = 143;
int n, r, d[N];
double p[N], f[N][R], g[N], pw[N][R];
void work() {
    memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g));
    int i, j; scanf("%d%d", &n, &r);
    for (i = 1; i <= n; i++) scanf("%lf%d", &p[i], &d[i]), pw[i][0] = 1;
    if (!r) return (void) puts("0.0000000000");
    for (i = 1; i <= n; i++) for (j = 1; j <= r; j++)
        pw[i][j] = pw[i][j - 1] * (1.0 - p[i]);
    f[1][0] = pw[1][r]; f[1][1] = g[1] = 1.0 - pw[1][r];
    for (i = 2; i <= n; i++) for (j = 0; j <= min(i, r); j++) {
        if (j) f[i][j] += f[i - 1][j - 1] * (1.0 - pw[i][r - j + 1]);
        if (i != j) f[i][j] += f[i - 1][j] * pw[i][r - j];
    }
    for (i = 2; i <= n; i++) for (j = 0; j <= min(i - 1, r); j++)
        g[i] += f[i - 1][j] * (1.0 - pw[i][r - j]);
    double ans = 0;
    for (i = 1; i <= n; i++) ans += g[i] * d[i];
    printf("%.10lf\n", ans);
}
int main() {
    int T; cin >> T; while (T--) work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值