传送门
首先看到这道题,我们发现想要求收集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;
}
标签:张卡,Packs,int,Expansion,ABC382E,概率,ff,now,DP
From: https://www.cnblogs.com/lwiwi/p/18651019