概率与期望的学习(更新中)


概率与期望

感谢 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\)

接下来有两种做法

  1. 交换求和号 + 等比数列

    \[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 \]

  2. 生成函数

    \[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

[WOJ3007] 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=E_1+E_2+\frac1{1-P_l-P_r}+\frac{P_l\times E_1}{1-P_l-P_r}+\frac{P_r\times E_2}{1-P_l-P_r} \]

因为每块牌都一模一样,显然对于所有长度相同的区间,它们的 \(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] 收集邮票

[WOJ3806] 收集邮票

设两个 DP 数组: \(f_i,g_i\) ,表示已经获得 \(i\) 张不同邮票后还需买 \(f_i\) 张邮票才能凑齐 \(n\) 张不同邮票,这 \(f_i\) 张邮票要花 \(g_i\)

根据这两个的定义,我们选择倒序 DP

根据“前置内容”中的定理,易得

\[f_i=f_{i+1}+\frac n{n-i} \]

因为每次不选到已经选过的前 \(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\) 张开始一直到最后花的钱,那么得到方程

\[g_i=\frac in(g_i+f_i+1)+\frac{n-i}n(g_{i+1}+f_{i+1}+1) \]

化简一下就行了

#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}\)

得到方程

\[f_{i,j}=\dfrac{ij}{ns}(1+f_{i,j})+\dfrac{(n-i)j}{ns}(1+f_{i+1,j})+\dfrac{i(s-j)}{ns}(1+f_{i,j+1})+\dfrac{(n-i)(s-j)}{ns}(1+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;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM