[ 题解 ] [ HNOI2015 ] [LuoguP3239] 亚瑟王 ( 概率dp )
HNOI2015 亚瑟王
题解
概率 dp
首先,设第 \(i\) 卡牌发动技能的概率为 \(f_i\),所有卡牌造成的总伤害的期望为 \(E_d\)。
那么,这张卡牌造成的伤害即为 \(f_i d_i\)。(\(d\) 为原题中的伤害)
由于数学期望的线性性质[1] \(E(X + Y) = E(X) + E(Y)\),可知:
那么,这题的目标即为求 \(f_i\)。
先考虑 \(f_1\)
显然,\(1 - p_1\) 为 \(1\) 号卡牌不发动的概率,因为
- 如果这张卡牌在这一局游戏中已经发动过技能,则
1.1. 如果这张卡牌不是最后一张,则跳过之(考虑下一张卡牌); 否则(是最后一张),结束这一轮游戏。
所以 \(1\) 号牌一直不出的概率为 \((1-p_1)^r\)。(\(r\) 为原题中游戏的轮数)
说明 \(1\) 号牌发动的概率 \(f_1 = 1 - (1 - p_1)^r\)。
再考虑 \(f_2\)
分类讨论:
-
假如 \(1\) 号卡牌发动了技能
\[f_2 = 1 - (1 - p_2)^{r - 1} \]因为 \(1\) 号卡牌发动了技能,那么:
- 否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第 \(i\) 张
2.1. 将其以 \(p_i\) 的概率发动技能。
2.2 如果技能发动,则对敌方造成 \(d_i\) 点伤害,并结束这一轮。
第一张牌发动了技能,根据
2.2
结束了这一轮,即与第二张牌发动的概率无关,所以这次为发动的概率不算在 \(f_2\) 中,所以得到指数为 \(r - 1\)。 - 否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第 \(i\) 张
-
假如 \(1\) 号卡牌没有发动技能
同 \(f_1\) 的情况,
\[f_2 = 1 - (1 - p_2)^r \]
再考虑接下来的 \(f_i\)
假设在第 \(i\) 张牌尝试发动前已有 \(j\) 张牌成功发动了技能,那么:
考虑到数据较小,
\(1 \leq T \leq 444, 1 \leq n \leq 220, 0 \leq r \leq 132, 0 < p_i < 1, 0 \leq d_i \leq 1000\)
要处理 \(f_i\) ,可以用动态规划。
设 \(\operatorname{dp}_{i,j}\) 表示前 \(i\) 张牌中,有 \(j\) 张发动了技能的概率。
分类讨论:
-
第 \(i\) 张牌发动了技能
-
因为有牌发动了技能,所以要保证 \(j > 0\)
-
求状态转移方程
不难看出,前 \(i\) 张牌中,有 \(j\) 张发动了技能的概率,应为前 \(i - 1\) 张牌中,有 \(j - 1\) 张发动了技能的概率乘上第 \(i\) 张牌发动的概率。
由前文推导到的 \(\small{f_i = 1 - (1 - p_i)^{r-j}}\) 可知第 \(i\) 张牌不发动的概率应该是 \(\small{f_i = 1 - (1 - p_i)^{r-j}}\) 但已经确定这张牌会发动,所以其概率实际为 \(f_i = 1 - (1 - p_i)^{r-j}\)。
得到转移方程 ①:
\[\operatorname{dp}_{i,j} = \operatorname{dp}_{i-1,j-1} \cdot (1 - (1 - p_i)^{r - j + 1})\quad(j > 0) \]
-
-
第 \(i\) 张牌没有发动技能
-
因为第 \(i\) 张牌没有发动技能,所以 \(j \leq i - 1 \iff i \neq j\)。
-
求状态转移方程
不难看出,前 \(i\) 张牌中,有 \(j\) 张发动了技能的概率,应为前 \(i - 1\) 张牌中,有 \(j\) 张发动了技能的概率乘上第 \(i\) 张牌不发动的概率。
第 \(i\) 张牌不发动的概率就为 \((1 - p_i) ^ {r - j}\)。
得出状态转移方程 ②:
\[\operatorname{dp}_{i,j} = \operatorname{dp}_{i-1,j} \cdot (1 - p_i)^{r - j}\quad(i \neq j) \]
-
用动态规划即可求出 \(\operatorname{dp}\)。
求出了 \(\operatorname{dp}\),要求 \(f\)。
显然,发动第 \(i\) 张牌的概率为前 \(i-1\) 张牌中,有 \(j\) 张发动了技能的概率乘上第 \(i\) 张牌发动的概率的和。(\(j\in\{x \mid 0 \leq x \leq i - 1, x\in\mathbb{Z} \}\))
求出了 \(f\),即可求出 \(E_d\)。
代码
#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
const int MAX_N = 4e2;
double p[MAX_N];
int d[MAX_N];
double dp[MAX_N][MAX_N];
double f[MAX_N];
double Pw[MAX_N][MAX_N];
int main()
{
int T;
std::cin >> T;
for (int t = 0; t < T; t++)
{
// Init
std::memset(dp, 0, sizeof(dp));
std::memset(f, 0, sizeof(f));
int n, r;
std::cin >> n >> r;
for (int i = 1; i <= n; i++)
std::cin >> p[i] >> d[i];
// Init
dp[1][0] = std::pow(1 - p[1], r);
dp[1][1] = 1 - dp[1][0];
f[1] = dp[1][1];
// Get dp
for (int i = 2; i <= n; i++)
{
for (int j = 0; j <= std::min(i, r); j++)
{
if (j > 0)
// f[i][j] = f[i - 1][j - 1] * (1 - (1 - p[i]) ^ (r - j + 1)) (j != 0) ==> do damage from i
dp[i][j] += dp[i - 1][j - 1] * (1 - std::pow(1 - p[i], r - j + 1));
if (i != j)
// f[i][j] = f[i - 1][j] * (1 - p[i]) ^ (r - j) (i != j) ==> no damage from i
dp[i][j] += dp[i - 1][j] * std::pow(1 - p[i], r - j);
}
}
// Get f
for (int i = 2; i <= n; i++)
for (int j = 0; j <= std::min(i - 1, r); j++)
f[i] += dp[i - 1][j] * (1 - std::pow(1 - p[i], r - j));
// Get ans
double ans = 0;
for (int i = 1; i <= n; i++)
ans += f[i] * d[i];
std::cout << std::fixed << std::setprecision(10) << ans << "\n";
}
return 0;
}