[HNOI2015] 亚瑟王 题解

前言

题目链接:洛谷

题意简述

一套按顺序的 \(n\) 张卡牌,每张卡牌都有一个技能,第 \(i\) 张卡牌的技能发动概率为 \(p_i\),如果成功发动,则会对敌方造成 \(d_i\) 点伤害,并弃置这张牌。一局游戏一共有 \(r\) 轮,或者当手中没有任何一张牌的时候结束。在每一轮中,按照顺序依次考虑剩下的卡牌,如果成功发动,那么对敌方造成伤害,弃置它,进入下一轮,否则考虑下一张牌,或者当这张牌就是最后一张牌时,进入下一轮。求这套卡牌在一局游戏中能造成的伤害的期望值。

\(1 \leq n \leq 220\)\(0 \leq r \leq 132\)\(0 < p_i < 1\)\(0 \leq d_i \leq 1000\)

题目分析

状压 DP 可以拿到部分分,但是没有任何前途。

显然可以对计数视角转化,考虑每一张牌在这一局游戏中,被打出的概率 \(\mathcal{P}_i\),那么答案可以表示为 \(\sum\mathcal{P}_id_i\)。考虑如何求 \(\mathcal{P}_i\)

我们发现对于第一张牌 \(\mathcal{P}_1\) 很容易求,就是 \(1-(1-p_1)^r\)

我不会求 $\mathcal{P}_1$

考虑它没有被打出,当且仅当每一次考虑到它时,都以 \(1-p_1\) 的概率没有打出,那么没有被打出的概率就是 \((1-p_1)^r\),对立事件即被打出了的概率即为 \(1-(1-p_1)^r\)

或者有人将它理解为一个 01 序列,每一位独立且有 \(p_1\) 的概率为 \(1\)\(1-(1-p_1)^r\) 表示至少存在一个 \(1\) 的概率,似乎这是不对的。考虑一个存在多个 \(1\) 的局面,其中第一个 \(1\)\(i\),那么我们会统计到 \(i+1\sim n\)\(000\ldots000\) 取遍 \(111\ldots111\) 的任意局面,这些概率之和为 \(1\),那么我们相当于统计到了 \(1\sim i\) 形成 \(000\ldots001\) 的局面然后马上停下的概率,也就是我们要求的。

发现其他 \(\mathcal{P}_i\) 似乎不好直接列式子,我们希望它也可以使用形如 \(1-(1-p_i)^k\) 的表达式来计算,其中 \(k\) 表示在 \(r\) 轮中,有 \(k\)\(1\sim i-1\) 都没有出牌,轮到了 \(i\) 的判定。但是 \(k\) 显然不是定值,那么我们就枚举 \(k\),这样就需要知道恰有 \(k\)\(1\sim i-1\) 都没有出牌的概率。这个似乎可以 DP。设 \(f_{i,j}\) 表示前 \(i\) 张牌中,出了 \(j\) 张牌的概率,于是有 \(r-j\)\(1\sim i-1\) 都没有出牌,即 \(k=r-j\)\(\mathcal{P}_i=\sum\limits_{j=0}^r f_{i-1,j}\Big(1-(1-p_i)^{r-j}\Big)\)

接下来问题变成了如何求 \(f_{i,j}\)。边界为 \(f_{1,0}=(1-p_1)^r\)\(f_{1,1}=1-f_{1,0}\)。从 \(f_{i-1}\) 转移到 \(f_{i,j}\),第 \(i\) 张牌在 \(r\) 轮中,可能出牌了,也可能没有出牌,分两种情况转移:

  1. \(i\) 出牌了。
    \(f_{i-1,j-1}\) 转移而来。那么会有 \(r-(j-1)\) 次判定到 \(i\)\(i\) 出牌的概率为 \(1-(1-p_i)^{r-j+1}\)

    \[f_{i,j}\gets f_{i-1,j-1}\Big(1-(1-p_i)^{r-j+1}\Big) \]

  2. \(i\) 没有出牌。
    \(f_{i-1,j}\) 转移而来。那么会有 \(r-j\) 次判定到 \(i\)\(i\) 每次都不出的概率为 \((1-p_i)^{r-j}\)

    \[f_{i,j}\gets f_{i-1,j}(1-p_i)^{r-j} \]

于是可以做到 \(\mathcal{O}(nr)\) 的时间复杂度解决本题。

代码

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

const int N = 225;
const double eps = 1e-13;

int n, r, d[N];
double p[N];
double pw[N][N], f[N][N], P[N];

void solve() {
    scanf("%d%d", &n, &r);
    for (int i = 1; i <= n; ++i)
        scanf("%lf%d", &p[i], &d[i]);
    for (int i = 1; i <= n; ++i) {
        pw[i][0] = 1;
        for (int j = 1; j <= r; ++j) {
            pw[i][j] = pw[i][j - 1] * (1 - p[i]);
            f[i][j] = 0;
        }
    }
    f[1][0] = pw[1][r], f[1][1] = P[1] = 1 - f[1][0];
    for (int i = 2; i <= n; ++i)
        for (int j = 0; j <= min(i, r); ++j) {
            f[i][j] = f[i - 1][j] * pw[i][r - j];
            if (j) f[i][j] += f[i - 1][j - 1] * (1 - pw[i][r - j + 1]);
        }
    for (int i = 2; i <= n; ++i) {
        P[i] = 0;
        for (int j = 0; j <= min(i - 1, r); ++j)
            P[i] += f[i - 1][j] * (1 - pw[i][r - j]);
    }
    double ans = 0;
    for (int i = 1; i <= n; ++i)
        ans += P[i] * d[i];
    printf("%.10lf\n", ans + eps);
}

signed main() {
    int t; scanf("%d", &t);
    while (t--) solve();
    return 0;
}
posted @ 2025-03-05 21:17  XuYueming  阅读(33)  评论(0)    收藏  举报