zoukankan      html  css  js  c++  java
  • [ 题解 ] [ HNOI2015 ] [LuoguP3239] 亚瑟王 ( 概率dp )

    HNOI2015 亚瑟王

    显示/隐藏 题面

    题解

    概率 dp

    首先,设第 (i) 卡牌发动技能的概率为 (f_i),所有卡牌造成的总伤害的期望为 (E_d)

    那么,这张卡牌造成的伤害即为 (f_i d_i)。((d) 为原题中的伤害)

    由于数学期望的线性性质[1] (E(X + Y) = E(X) + E(Y)),可知:

    [E_d = sum_{i=1}^n f_i d_i ]

    那么,这题的目标即为求 (f_i)

    先考虑 (f_1)

    显然,(1 - p_1)(1) 号卡牌不发动的概率,因为

    1. 如果这张卡牌在这一局游戏中已经发动过技能,则
      1.1. 如果这张卡牌不是最后一张,则跳过之(考虑下一张卡牌); 否则(是最后一张),结束这一轮游戏。

    所以 (1) 号牌一直不出的概率为 ((1-p_1)^r)。((r) 为原题中游戏的轮数)

    说明 (1) 号牌发动的概率 (f_1 = 1 - (1 - p_1)^r)

    再考虑 (f_2)

    分类讨论:

    1. 假如 (1) 号卡牌发动了技能

      [f_2 = 1 - (1 - p_2)^{r - 1} ]

      因为 (1) 号卡牌发动了技能,那么:

      1. 否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第 (i)
        2.1. 将其以 (p_i) 的概率发动技能。
        2.2 如果技能发动,则对敌方造成 (d_i) 点伤害,并结束这一轮。

      第一张牌发动了技能,根据 2.2 结束了这一轮,即与第二张牌发动的概率无关,所以这次为发动的概率不算在 (f_2) 中,所以得到指数为 (r - 1)

    2. 假如 (1) 号卡牌没有发动技能

      (f_1) 的情况,

      [f_2 = 1 - (1 - p_2)^r ]

    再考虑接下来的 (f_i)

    假设在第 (i) 张牌尝试发动前已有 (j) 张牌成功发动了技能,那么:

    [f_i = 1 - (1 - p_i)^{r-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) 张发动了技能的概率。

    分类讨论:

    1. (i) 张牌发动了技能

      1. 因为有牌发动了技能,所以要保证 (j > 0)

      2. 求状态转移方程

        不难看出,前 (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) ]

    2. (i) 张牌没有发动技能

      1. 因为第 (i) 张牌没有发动技能,所以 (j leq i - 1 iff i eq j)

      2. 求状态转移方程

        不难看出,前 (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_i = sum_{j=0}^{i -1} operatorname{dp}_{i - 1, j} cdot (1 - (1 - p_i)^{r - j}) ]

    求出了 (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;
    }
    

    参考


    1. 如何理解数学期望的线性性质? ↩︎

  • 相关阅读:
    crawler碎碎念4 关于python requests、Beautiful Soup库、SQLlite的基本操作
    另类爬取表格数据
    如何选择kmeans中的k值——肘部法则–Elbow Method和轮廓系数–Silhouette Coefficient
    欧几里得距离
    数据导入+欧式距离计算+互信息计算
    轮廓系数
    肘部法则
    利用键值对进行排序的操作
    NMI计算
    彻底搞懂 C# 的 async/await
  • 原文地址:https://www.cnblogs.com/zhangtianli/p/luogu3239.html
Copyright © 2011-2022 走看看