概率与期望
感谢 FSYo巨佬的博客
前置内容
- 若有一次操作中事件 A 发生的概率为 \(P(A)\),那么要事件 A 发生,则期望 \(\dfrac1{P(A)}\) 次操作
例:求抛一个骰子抛到 4 ,期望抛多少次?
因为 \(P(抛到4)=\dfrac16\) ,所以答案就是 6 次。
或者这么想:
设 \(P_k\) 为抛第 k 次才得到 4 的概率,也就是前 \(k-1\) 次均不是 4 ,那么 \(P_k=(\dfrac56)^{k-1}\times\dfrac16\)
所以 \(Ans=\sum\limits_{k=1}^\infty P_k\times k=\dfrac16\sum\limits_{k=1}^\infty (\dfrac56)^{k-1}\times k\)
接下来有两种做法
- 交换求和号 + 等比数列
\[Ans=\frac16\sum\limits_{k=1}^\infty (\frac56)^{k-1}\times k =\frac16\sum\limits_{k=1}^\infty\sum_{i=1}^k(\frac56)^{k-1} =\frac16\sum_{i=1}^\infty\sum_{k=i}^\infty(\frac56)^{k-1}\\ Ans=\frac16\sum_{i=1}^\infty \lim_{n\to\infty}((\frac56)^{i-1}\times\frac{1-(\frac56)^n}{1-\frac56}) =\sum_{i=1}^\infty(\frac56)^{i-1} =\sum_{i=0}^\infty(\frac56)^{i}\\ Ans=\lim_{n\to\infty}(1\times\frac{1-(\frac56)^n}{1-\frac56})=6 \]
- 生成函数
\[Ans=\frac16\sum\limits_{k=1}^\infty (\frac56)^{k-1}\times k =\frac16\sum\limits_{k=0}^\infty (\frac56)^k\times(k+1) \]令 \(x=\dfrac56\) ,那么\[Ans=\frac16\sum\limits_{k=0}^\infty (k+1)\ x^k =\frac16\times(1+2x+3x^2+4x^3+\cdots)\\ Ans=\frac16\times(1+x+x^2+x^3+\cdots)^2 =\frac16\times(\frac1{1-x})^2\\ Ans=\frac16\times\frac1{(1-\frac56)^2}=6 \]
例题
[Luogu UVA10529] Dumb Bones
概率 + 最优,考虑一个区间DP。
枚举这个区间里最后摆放的一块骨牌的位置,把原区间分割成左右两个子区间。
设原区间期望最少摆放次数为 \(E\) ,左区间为 \(E_1\) ,右区间 \(E_2\) 。此时最后一块骨牌还没有放上去,那么设这个空位的位置是 \(x\)
已知每块骨牌向左倒的概率是 \(P_l\) ,向右是 \(P_r\)
单独看 \(x\) 这一个骨牌,它不倒的概率是 \(1-P_l-P_r\) ,那么使它不倒的期望摆放次数是 \(\dfrac1{1-P_l-P_r}\)
注意这个次数里:最后一次是不倒的,之前的有些向左有些向右。因为向左的概率是 \(P_l\) ,所以向左倒的次数就是 \(\dfrac1{1-P_l-P_r}\times P_l\) 。由于每向左倒一次,左区间的就要全部重新摆放。左区间放好一次需要 \(E_1\) 个摆放,所以一共左区间要摆 \(\dfrac{P_l}{1-P_l-P_r}\times E_1\) 次。同理右区间一共要放置 \(\dfrac{P_r}{1-P_l-P_r}\times E_2\) 次。
还有放 \(x\) 之前需要先把左右区间都摆好一次,共 \(E_1+E_2\) 个摆放。
状态转移方程
因为每块牌都一模一样,显然对于所有长度相同的区间,它们的 \(E\) 都是相同的
把状态设置为区间长度,就只有不同长度的区间才需要转移
#include <cstdio>
#include <iostream>
using namespace std;
double pl, pr;//向左、向右倒的概率
double f[1030];
double dfs(int n) {//长度为 n 的区间
double &E = f[n], E1, E2;//还是引用
if (E > -0.5) return E;//让 f[0]=0.0 和 f[1]=1.0/(1.0-pl-pr) 都可以自然转移
E = 1e20;
for (int i=1; i<=n; ++i) {
E1 = dfs(i-1), E2 = dfs(n-i);//左、右区间的答案
E = min(E, E1 + E2 + f[1] + pl*E1*f[1] + pr*E2*f[1]);
} return E;
}
signed main() {
ios::sync_with_stdio(false);cin.tie(nullptr);
register int n;
while (cin>>n && n) {
cin >> pl >> pr;
for (int i=2; i<=n; ++i) f[i] = -1.0;//注意细节
f[1] = 1.0 / (1.0 - pl - pr);//同时 f[0]=0
printf("%.2f\n", dfs(n));
} return 0;
}
[WOJ2276] 挑战
设 \(f_{i,j,t}\) 表示,前 i 次中,成功了 j 次,剩余 t 个单位的包包空间,的概率。
观察发现最多有 n 个地图碎片,也就是最多只需要 n 个单位的包包空间,所以超过 n 的包包空间的情况都可以合并到 n 里去。这样就不会爆空间了。
注意下标 t 为负的情况。
#include <iostream>
#include <iomanip>
using namespace std;
#define N 230
int n, L, k, a[N];
double p[N], f[N][N][N<<1];
signed main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> n >> L >> k;
for (int i=1; i<=n; ++i) cin >> p[i], p[i]/= 100.0;
for (int i=1; i<=n; ++i) cin >> a[i];
if (k > n) k = n;
f[0][0][N+k] = 1.0;//易得边界值
for (int i=1; i<=n; ++i) {//枚举第 i 次
for (int j=0; j<i; ++j) {//枚举第 1 ~ i-1 次中共成功了 j 次
int l = N+k-i, r = N+n;//允许的背包容量范围
for (int t=l; t<=r; ++t) {//枚举当前背包容量 t
f[i][j][t] += f[i-1][j][t] * (1.0 - p[i]);//这一次没成功
f[i][j+1][min(N+n, t+a[i])] += f[i-1][j][t] * p[i];//这一次成功了
}
}
}
double ans = 0;
for (int j=L; j<=n; ++j)
for (int t=0; t<=n; ++t)
ans += f[n][j][N+t];
cout << fixed << setprecision(6) << ans; return 0;
}
[Luogu P4550] 收集邮票
设两个 DP 数组: \(f_i,g_i\) ,表示已经获得 \(i\) 张不同邮票后还需买 \(f_i\) 张邮票才能凑齐 \(n\) 张不同邮票,这 \(f_i\) 张邮票要花 \(g_i\) 元
根据这两个的定义,我们选择倒序 DP
根据“前置内容”中的定理,易得
因为每次不选到已经选过的前 \(i\) 张的概率是 \(\dfrac{n-i}n\) 。
考虑我们买的单价是倒序的,跟 DP 顺序一致。最后买的邮票 1 元,倒数第 2 张 2 元 … 第 1 张邮票 \(f_0\) 元。
假设现在已经拥有 \(i\) 种不同的邮票,一共有 \(k\) 张。想一想买第 \(k+1\) 张的情况。
有 \(\dfrac in\) 的概率与前 \(k\) 张重复,那就还是只有 \(i\) 种邮票,之后就还要买 \(f_i\) 张共 \(g_i\) 元才能凑齐 \(n\) 种。所以第 \(k+1\) 张就是倒数第 \(f_i+1\) 张,花费 \(f_i+1\) 元。从第 \(k+1\) 张开始一直到最后一共就要花 \(g_i+f_i+1\) 元。
有 \(\dfrac {n-i}n\) 的概率是新邮票,就有 \(i+1\) 种邮票了,之后就还要买 \(f_{i+1}\) 张共 \(g_{i+1}\) 元才能凑齐 \(n\) 种。那么第 \(k+1\) 张就是倒数第 \(f_{i+1}+1\) 张,花费 \(f_{i+1}+1\) 元。从第 \(k+1\) 张开始一直到最后一共就要花 \(g_{i+1}+f_{i+1}+1\) 元。
因为 \(g_i\) 表示从第 \(k\) 张开始一直到最后花的钱,那么得到方程
化简一下就行了
#include <stdio.h>
#define N 10030
double f[N], g[N], n;//这里要注意一下精度问题,我们把 n 设为 double
signed main() {
scanf("%lf", &n);//读成 double
for (int i=n-1; ~i; --i) {//这里易证边界:f[n]=g[n]=0,就不多加赘述了
f[i] = f[i+1] + n/(n-i);
g[i] = g[i+1] + f[i+1] + i*f[i]/(n-i) + n/(n-i);//为了精度,改变一下乘除法顺序
} printf("%.2f", g[0]);
return 0;
}//发现数组每次只用了两位,所以程序还可以进一步优化
[WOJ3083] 收集宝石
与上题类似
设 \(f_{i,j}\) 表示已经有 \(i\) 种宝石、 \(j\) 个不同的下水道后,还需要的期望天数,倒序 DP。
假设现在已经过去 \(k\) 天(正序),对于第 \(k+1\) 天,则
有 \(\dfrac{ij}{ns}\) 的概率转移到 \(f_{i,j}\)
有 \(\dfrac{(n-i)j}{ns}\) 的概率转移到 \(f_{i+1,j}\)
有 \(\dfrac{i(s-j)}{ns}\) 的概率转移到 \(f_{i,j+1}\)
有 \(\dfrac{(n-i)(s-j)}{ns}\) 的概率转移到 \(f_{i+1,j+1}\)
得到方程
方程中的 +1 是指第 \(k+1\) 天当天,如果还不能理解方程请回看上一题
化简即可
#include <stdio.h>
double f[1003][1003], n, s;
signed main() {
scanf("%lf%lf", &n, &s);
for (int i=n; ~i; --i)
for (int j=s; ~j; --j) {
if (i==n && j==s) continue;//存在边界:f[n][s]=0
f[i][j] = (
n*s
+ (n-i)*j * f[i+1][j]
+ i*(s-j) * f[i][j+1]
+ (n-i)*(s-j) * f[i+1][j+1]
) / (n*s - i*j);
} printf("%.4f", f[0][0]); return 0;
}
[SCOI2008] 奖励关
状压 DP
设 \(f_{x,s}\) 表示已经走了 \(x\) 次,前 \(x\) 次收集宝物的状态是 \(s\) ,第 \(x+1\) 到 第 \(k\) 轮的最大期望得分。
还是一样地倒着 DP。
枚举所有宝物 \(i\) ,每个 \(i\) 的概率是 \(\dfrac1n\) ,计算期望时就可以先把和算出来最后取平均值。
如果当前状态可以吃,则分为吃与不吃两种状态,取较大值为优;如果不能吃,就直接转移。
#include <stdio.h>
#include <algorithm>
int n, k;
int p[18], s[18];
double f[103][70000];
double dfs(int x, int state) {//这里我采取了记忆化搜索的写法,一般的for循环递推也是可以的
if (x >= k) return 0;//易证边界f[k][...]=0
double &ret = f[x][state];//引用
if (ret > -0.5) return ret;//记忆化
ret = 0;
for (int i=1; i<=n; ++i) {
if ((s[i] & state) == s[i]) {//如果可以吃i,就有吃与不吃两种状态
ret += std::max(dfs(x+1, state|(1<<(i-1))) + p[i], dfs(x+1, state));
//这里其实是退栈的时候计算答案,所以本质就是倒序DP的
} else ret += dfs(x+1, state);//如果不能吃i
} return ret /= n;//取平均值(期望值)
}
signed main() {
scanf("%d%d", &k, &n);
for (int i=1, tmp; i<=n; ++i) {
scanf("%d", p+i);
while (true) {
scanf("%d", &tmp);
if (!tmp) break;
s[i] |= 1 << (tmp - 1);
}
}
for (int i=0; i<=k; ++i)
for (int j=0; j<=(1<<15); ++j)
f[i][j] = -1;//初始化
printf("%.6f", dfs(0, 0)); return 0;
}