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

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

f[i][j]=p[i]f[i1][j1]+(1p[i])f[i1][j]

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

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

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

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

f[i]=1+k=0min(n,i)p[k]f[ik]

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

f[i]=11p[0](1+k=1min(n,i)p[k](f[ik]+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 @   孤枕  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
我命令你,喜欢我!