數學概率期望總結


由於本人很菜概率期望學的不是很好所以特別寫一篇總結。。。

收集郵票

這個題代碼比較短但是思維含量的確挺高的,加之部分網上題解對於轉移方程的描述過於顯然,所以可能會有人想不明白這題。

先考慮能不能直接推式子計算,你會發現不僅式子不好推而且好像根本算不出來,於是考慮另一種比較套路的做法,dynamic programming。

直接設當前買到\(i\)種郵票的期望花費為\(f_i\),然后進行轉移,會發現又被卡住了,因為買某張郵票的時候錢數與這是第幾張郵票有關系,並且買到的有可能是之前已經買到過的郵票,這兩者會導致什么情況呢,就是dp有后效性。

那是不是不能dp呢?普通式子好像還是沒有什么可以推的於是思考如何消除后效性,假設我們已經知道了買完這次之后還期望買多少次能買完這\(n\)張,那完全可以將費用提前計算然后消除后效性。

於是可以設\(f_i\)為買到\(i\)種郵票后,買齊\(n\)種的期望購買次數,設\(g_i\)表示買完\(i\)種后,買齊\(n\)種的期望購買花費,先考慮\(f_i\)的轉移,因為它是用來輔助\(g\)數組進行轉移的,想一下買完這張郵票之后會有什么影響,有可能會買到曾經買到的種類,概率為\(\frac{i}n\),然后此時要買齊\(n\)種的期望還是\(f_i\),初學概率dp的時候可能會感覺這種轉移有些問題,自己還能轉移自己??其實這只是用來推式子,從而將一個無窮盡的過程可視化,因為買郵票完全可以每次都買不齊然后一直買一直買。。。。雖然這是我自己口胡的但是我的確這么理解,然后考慮,買到了一種原來沒有買到過的郵票,概率為\(\frac{n-i}n\),然后此時就有了\(i+1\)種郵票,買齊\(n\)種的期望變成了\(f_{i+1}\),也可以從集合的角度去理解,花費1由集合\(i\)擴展為集合\(i+1\),個人感覺這樣比較好理解,最后這次花費了一次購買,概率顯然是1,所以要加一個1,即

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

移項合並后發現可以得到\(f_i\)的遞推式,

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

接下來就是\(g_i\)的轉移,這個有了\(f\)數組之后就可以通過費用提前計算來消除后效性,轉移的思考與上邊類似,只說一下不太一樣的地方,如果這次買到了曾經買過的\(i\)種,那么以后要再買的\(f_i\)張郵票都要把花費加1,一共加\(f_i\)個費用,這就是費用提前計算,然后進行轉移便可。

#include<cstdio>
const int N=1e4+10;
typedef double db;
db f[N],g[N];
int main(){
	freopen("D.in","r",stdin);
	freopen("D.out","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=n-1;i>=0;i--)
		f[i]=f[i+1]+1.0*n/(n-i);
	for(int i=n-1;i>=0;i--)
		g[i]=g[i+1]+f[i+1]+f[i]*i/(n-i)+1.0*n/(n-i);
	printf("%.2lf\n",g[0]);
	return 0;
}

骰子基礎版

將一個骰子扔\(n\)次,然后問點數和大於等於\(X\)的概率。

這個感覺歸入到計數dp里會好點??emm考慮到扔\(n\)次的情況總數是固定的,所以可以計算出合法的情況總數然后用最原始的計算概率的方法算一下。
計算的時候就定義\(f_{i,j}\)表示當前是第\(i\)次扔,點數和為\(j\)的方案數是多少然后轉移就行。

#include<iostream>
#define ll long long
using namespace std;
const int lqs=200;
ll f[lqs][lqs];
ll gcd(ll a,ll b){
	return b==0?a:gcd(b,a%b);
}
int main(){
	int n,x;
	cin>>n>>x;
	for(int i=1;i<=6;i++)
		f[1][i]=1;
	for(int i=2;i<=n;i++)
	for(int j=1;j<=6*i;j++)
	for(int k=1;k<=6;k++)
		if(j>k)
			f[i][j]+=f[i-1][j-k];
	ll ans=0,cnt=1;
	for(int i=1;i<=n;i++)cnt*=6;
	for(int i=x;i<=6*n;i++)
		ans+=f[n][i];
	if(ans==0)cout<<"0";
	else if(ans==cnt)cout<<"1";
	else {
		ll g=gcd(ans,cnt);
		cout<<ans/g<<'/'<<cnt/g;
	}
	return 0;
}

聰聰與可可

一道比較好的概率dp題,不過是記憶化搜索實現的。
其實感覺,最難搞的是貓的走法,因為對於不同的貓的位置和老鼠的位置,貓的走法應該不是一樣的,所以預處理出\(nxt\)數組表示貓在\(i\)老鼠在\(j\)時貓的下一步走法,然后就能搞一下了。
不妨設\(f_{i,j}\)表示貓在\(i\)老鼠在\(j\)時抓住老鼠的期望步數,不難得出當\(i==j\)的時候該值為0,否則當\(i\)用一步或兩步可以走到\(j\)的時候,該值為1,其余情況可以進行轉移,主要就考慮老鼠的下一步往什么方向走就行了,其余的還挺好理解。

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
struct Edge{
	int to,nxt;
}e[N<<1];
int h[N],idx;
void Ins(int a,int b){
	e[++idx].to=b;e[idx].nxt=h[a];h[a]=idx;
}
int nxt[N][N],dis[N][N];
void bfs(int s){
	queue<int> q;q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=h[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[s][v])continue;
			dis[s][v]=dis[s][u]+1;
			q.push(v);
		}
	}
	dis[s][s]=0;
}
double f[N][N];
int d[N];
double dfs(int s,int t){
	if(f[s][t])return f[s][t];
	if(s==t)return 0;
	int fir=nxt[s][t];
	int sec=nxt[fir][t];
	if(fir==t||sec==t)return 1;
	f[s][t]=1;
	for(int i=h[t];i;i=e[i].nxt){
		int v=e[i].to;
		f[s][t]+=dfs(sec,v)*1.0/(d[t]+1);
	}
	f[s][t]+=dfs(sec,t)*1.0/(d[t]+1);
	return f[s][t];
}
int main(){
	int n,m,s,t;
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		Ins(a,b);Ins(b,a);
		d[a]++;d[b]++;
	}
	memset(nxt,0x3f,sizeof(nxt));
	for(int i=1;i<=n;i++)bfs(i);
	for(int i=1;i<=n;i++){
		for(int j=h[i];j;j=e[j].nxt){
			int v=e[j].to;
			for(int k=1;k<=n;k++)if(dis[i][k]==dis[v][k]+1)nxt[i][k]=min(nxt[i][k],v);
		}
	}
	printf("%.3lf\n",dfs(s,t));
	return 0;
}

OSU

這題也是推一下式子吧,也是考慮用一步差分,長度從\(x\)\(x+1\)的貢獻變化是\((x+1)^3-x^3\)然后推一下式子就能得到這個式子是跟\(x^2\)\(x\)有關系的,但是注意期望的平方不等於平方的期望,所以要再去推導一下\(x^2\)\(x\)

#include<cstdio>
#include<iostream>
typedef double ll;
using namespace std;
int main(){
	int n;
	scanf("%d",&n);
	ll ans=0,E1=0,E2=0;
	for(int i=1;i<=n;i++){
		ll p;
		scanf("%lf",&p);
		ans+=(3*E1+3*E2+1)*p;
		E2=(E2+2*E1+1)*p;
		E1=(E1+1)*p;
	}
	printf("%.1lf",ans);
}

守衛者的挑戰

一道比較顯然的概率\(dp\)?,根據題意可以設出狀態轉移方程\(f_{i,j,k}\)表示當前打到第\(i\)關,背包容量還有\(j\),贏了\(k\)次的概率有多少。背包容量可能是負的所以需要平移一下坐標原點

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=400+10;
typedef double db;
db f[205][N][205],p[N];
int typ[N];
int main(){
	int n,L,K;
	scanf("%d%d%d",&n,&L,&K);
	for(int i=1;i<=n;i++){
		int x;scanf("%d",&x);
		p[i]=1.0*x/100;
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&typ[i]);
	}
	f[0][n][0]=1.0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=n*2;j++){
			f[i][j][0]+=f[i-1][j][0]*(1-p[i]);
			for(int k=1;k<=i;k++){
				if(typ[i]+j>=0){
					f[i][min(j+typ[i],2*n)][k]+=f[i-1][j][k-1]*p[i];
					f[i][j][k]+=f[i-1][j][k]*(1-p[i]);
				}
			}
		}
	}
	db ans=0;
	for(int i=n-K;i<=n*2;i++)
		for(int j=L;j<=n;j++)
			ans+=f[n][i][j];
	printf("%.6lf\n",ans);
	return 0;
}


Easy

某一天WJMZBMR在打osu~~~但是他太弱逼了,有些地方完全靠運氣:(
我們來簡化一下這個游戲的規則
有n次點擊要做,成功了就是o,失敗了就是x,分數是按comb計算的,連續a個comb就有aa分,comb就是極大的連續o。比如ooxxxxooooxxx,分數就是22+4*4=4+16=20。
Sevenkplus閑的慌就看他打了一盤,有些地方跟運氣無關要么是o要么是x,有些地方o或者x各有50%的可能性,用?號來表示。比如oo?xx就是一個可能的輸入。
那么WJMZBMR這場osu的期望得分是多少呢?比如oo?xx的話,?是o的話就是oooxx => 9,是x的話就是ooxxx => 4 期望自然就是(4+9)/2 =6.5了

和上邊的題目其實差不多,只在是?的時候求一下期望然后其余情況照常處理即可。

#include<cstdio>
#include<iostream>
using namespace std;
const int N=3e5+10;
char s[N];
int main(){
	int n;
	cin>>n>>s;
	double ans=0,len=0;
	for(int i=0;i<n;i++){
		if(s[i]=='o'){
			ans+=2*len+1;
			len++;
		}else if(s[i]=='x')
			len=0;
		else {
			ans+=(2*len+1)*0.5;
			len=(len+1)*0.5;
		}
	}
	printf("%.4lf",ans);
}

單選錯位

因為每道題只跟與它交換的那個題有關系,所以考慮一道題移到另一道題的位置上會發生什么,假設選項較小的那個有\(Min\)個,較大的有\(Max\)個,那么選對的概率就是\(\frac{1}{Min}\times \frac{Min}{Max}=\frac{1}{Max}\),然后,然后就沒了。

卡牌游戲

這個問題我們發現,這個游戲中,某個人贏的概率與場上還剩誰沒什么關系,只跟場上還有多少人和庄家是誰有關,進一步的說,與庄家是誰也沒啥關系,只跟庄家與最后贏的這個人的相對位置有關,所以可以定義狀態\(f_{i,j}\)為場上還剩下\(i\)個人,和庄家距離\(j\)的人獲勝的概率,然后枚舉抽到的卡片進行轉移,假設抽完卡后下一輪庄家的位置為\(p\),討論\(p\)\(j\)的關系,如果\(p==j\)不管,\(p>j\)那兩人的距離就是\(p-j\),否則是\(i-(p-j)\)就是環的另一半的長度。

#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=60;
int card[N];
double f[N][N];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d",&card[i]);
	f[1][1]=100;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			for(int k=1;k<=m;k++){
				int pos=card[k]%i;
				if(pos==0)pos+=i;
				if(pos>j)f[i][j]+=f[i-1][i-pos+j]/m;
				else if(pos<j)f[i][j]+=f[i-1][j-pos]/m;
			}
		}
	}
	for(int i=1;i<=n;i++)printf("%.2lf%% ",f[n][i]);
	return 0;
}

換教室

這個題可能跟普通的dp套路差不多吧,首先把題中的限制都扔到狀態里邊,定義\(f_{i,j,k}\)為考慮前\(i\)個課,申請\(j\),第\(i\)次是否申請,然后轉移就行了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
int dis[N][N],c[N],d[N];
double p[N],f[N][N][2];
int main(){
	memset(dis,0x3f,sizeof(dis));
	int n,m,v,e;
	scanf("%d%d%d%d",&n,&m,&v,&e);
	for(int i=1;i<=n;i++)scanf("%d",&c[i]);
	for(int i=1;i<=n;i++)scanf("%d",&d[i]);
	for(int i=1;i<=n;i++)scanf("%lf",&p[i]);
	for(int i=1;i<=v;i++)dis[i][i]=0;
	for(int i=1;i<=e;i++){
		int a,b,w;
		scanf("%d%d%d",&a,&b,&w);
		dis[a][b]=dis[b][a]=min(dis[a][b],w);
	}
	for(int k=1;k<=v;k++)
		for(int i=1;i<=v;i++)
			for(int j=1;j<=v;j++)
				dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++)
			f[i][j][0]=f[i][j][1]=1e30;
	f[1][1][1]=f[1][0][0]=0;
	for(int i=2;i<=n;i++)
		for(int j=0;j<=m;j++){
			f[i][j][0]=min(f[i-1][j][0]+dis[c[i-1]][c[i]],f[i-1][j][1]+p[i-1]*dis[d[i-1]][c[i]]+(1-p[i-1])*dis[c[i-1]][c[i]]);
			if(j)f[i][j][1]=min(f[i-1][j-1][0]+p[i]*dis[c[i-1]][d[i]]+(1-p[i])*dis[c[i-1]][c[i]],f[i-1][j-1][1]+p[i-1]*p[i]*dis[d[i-1]][d[i]]+(1-p[i-1])*p[i]*dis[c[i-1]][d[i]]+p[i-1]*(1-p[i])*dis[d[i-1]][c[i]]+(1-p[i-1])*(1-p[i])*dis[c[i-1]][c[i]]);
		}
	double ans=1e30;
	for(int i=0;i<=m;i++)ans=min(ans,min(f[n][i][0],f[n][i][1]));
	printf("%.2lf\n",ans);
	return 0;
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM