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 eq 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 eq j) ]
-
用动态规划即可求出 (operatorname{dp})。
求出了 (operatorname{dp}),要求 (f)。
显然,发动第 (i) 张牌的概率为前 (i-1) 张牌中,有 (j) 张发动了技能的概率乘上第 (i) 张牌发动的概率的和。((jin{x mid 0 leq x leq i - 1, xinmathbb{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 << "
";
}
return 0;
}