关于此题[ABC382E] Expansion Packs 概率DP的一些总结

传送门
首先看到这道题,我们发现想要求收集K个卡牌的期望开包数,必须要先求出每个包开出0~n张卡各自的概率,于是预示着这道题将要进行两次概率DP。
首先我们求每个包开出0~n张卡各自的概率。这个很好求,我们假设f[i][j]表示前\(i\)张卡中开出\(j\)张卡的概率,那么显然有:

\(f[i][j] = p[i] * f[i-1][j-1] + (1-p[i]) * f[i-1][j]\)

当然可以通过倒序枚举的方式将f压到一维。我这里用的是滚动数组

然后就是求收集K个卡牌的期望开包数。我们假设\(ff[i]\)表示收集\(i\)张卡牌期望开的包数。

做此类期望,或者说概率DP的题目,我们要注意一个思维,即假设我们开了上帝视角,也就是假设我们目前已经知道了所有的\(ff[i]\),仅仅是希望探求\(ff[i]\)\(ff\)数组中其它元素的关系,并且借此来写出表达式(关系式),这就是我们要的递推式。个人觉得这是比较好能够理解的方式。

那么,我们现在假设\(ff[i]\)已知,试着考虑,对于目前摆在我们面前的这个包,我们即将打开它,那么此时我并不知道这个包中到底有多少张卡,但是我知道每个卡的数量对应的概率。我们假设包中有\(j\)张卡,那么对应的概率就是\(f[now][j]\),于是\(ff[i]\)就可以由\(ff[i-j]\)贡献而来。而包中0~n张卡都有可能,所以我们得到关系式:

\(f[i] = 1+\sum_{k=0}^{min(n,i)}p[k]*f[i-k]\)

注意,这里我用p[k]来表示f[now][k],即我们第一遍DP求出来的概率,而不是题目中输入的概率!这里的1是因为我一定要打开面前的这个包,所以期望的开包数一定会加1。我们发现这个式子两端都有f[i],于是我们需要将f[i]提出来才能得到DP能使用的递推式,化简得到:

$ f[i] = \frac{1}{1-p[0]}(1+\sum_{k=1}^{min(n,i)}p[k]*(f[i-k]+1))$

然后就可以解决这道题了。
代码:

#include<bits/stdc++.h>

using namespace std;

int n,m;
double p[5010],ans,sum;
double f[2][5010];
double ff[5010];

int main() {
    cin >> n >> m;
    for(int i = 1;i <= n;i++) cin >> p[i],p[i] /= 100;
    int now = 0,last = 1;
    f[now][0] = 1-p[1];
    f[now][1] = p[1];
    for(int i = 2;i <= n;i++) {
        swap(now,last);
        f[now][0] = f[last][0] * (1 - p[i]);
        for(int j = 1;j <= i;j++) {
            f[now][j] = p[i] * f[last][j-1] + (1 - p[i]) * f[last][j];
        }
    }

    for(int i = 1;i <= m;i++) {
        ff[i] = 1;
        for(int j = 1;j <= min(n,i);j++) {
            ff[i] += f[now][j] * ff[i - j];
        }
        ff[i] /= 1 - f[now][0];
    }

    printf("%.16lf",ff[m]);

    return 0;
}
posted @ 2025-01-03 22:01  孤枕  阅读(43)  评论(0)    收藏  举报