概率與期望的學習(更新中)


概率與期望

感謝 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