P3239:「HNOI2015」亚瑟王题解

解法

一道概率期望类 DP。

我们设状态不考虑当前轮数:这样太难设计了,我太菜不会。考虑设计全局的状态:\(f[i][j]\)意义为考虑前\(i\)张卡牌,且游戏结束时只发动了\(j\)张纸牌的概率。那么答案就是:

\[ ans = \sum_{i = 1}^n d_i \sum_{j = 0}^r dp[i-1][j] * [1 – (1 – p_i)^{r – j}] \]

分析这个式子:期望可以考虑线性叠加每一个点的贡献,威力乘上成功发动的概率;成功的概率可以分成\(j\)张牌成功发动的成功概率,计算连续失败的概率之后补集转换即可。

现在来考虑\(dp\)的求法。首先注意到这些纸牌都排好顺序了:

\[ dp[i][j] = dp[i-1][j] * (1-p_i)^{r – j} + dp[i-1][j-1] * (1 – (1 – p_i)^{r – (j – 1)}) \]

解读一下这个式子:

  • 从\(dp[i-1][j]\)进行转移:这个纸牌没有发动,当且仅当剩余的\(r-j\)轮里没有发动,所以写上补集概率的乘方。
  • 从\(dp[i-1][j-1]\)进行转移:这张纸牌发动(按顺序),当且仅当它在剩下的\(r-(j-1)\)轮中有发动的可能,所以还是补集转换。

代码

// P3239.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 300;
int T, n, r, di[MAX_N];
double pi[MAX_N], dp[MAX_N][MAX_N], f[MAX_N];

double quick_power(double bas, int tim)
{
    double ans = 1;
    while (tim)
    {
        if (tim & 1)
            ans = ans * bas;
        bas = bas * bas;
        tim >>= 1;
    }
    return ans;
}

int main()
{
    scanf("%d", &T);
    while (T--)
    {
        memset(dp, 0, sizeof(dp)), memset(f, 0, sizeof(f));
        scanf("%d%d", &n, &r);
        for (int i = 1; i <= n; i++)
            scanf("%lf%d", &pi[i], &di[i]);
        dp[1][0] = quick_power(1 - pi[1], r);
        dp[1][1] = f[1] = 1 - dp[1][0];
        for (int i = 2; i <= n; i++)
        {
            f[i] = 0;
            for (int j = 0; j <= r; j++)
            {
                dp[i][j] = dp[i - 1][j] * quick_power(1 - pi[i], r - j);
                if (j > 0)
                    dp[i][j] += dp[i - 1][j - 1] * (1 - quick_power(1 - pi[i], r - j + 1));
                f[i] += dp[i - 1][j] * (1 - quick_power(1 - pi[i], r - j));
            }
        }
        double ans = 0;
        for (int i = 1; i <= n; i++)
            ans += f[i] * di[i];
        printf("%.10lf\n", ans);
    }
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *