前言
概率期望是\(OI\)數學的一個比較難的考點,凡遇期望藍題往上,今年CSPS很可能會考,所以在這里做一個小總結。
概率簡介
自然界中的現象
自然界中的現象可以分為隨機現象和確定性現象。
-
隨機現象:在一定條件下,可能出現多種結果,在試驗之前無法得知確切結果,無法預知。
-
確定性現象:在一定條件下必然會出現的現象。
-
概率論和數理統計就是研究揭示隨機現象統計規律性的一門自然學科。
隨機試驗的特點
-
試驗可以在相同條件下重復進行。
-
試驗結果多種,且事先知道所有結果。
-
試驗后出現的結果隨機,無法控制。
-
通常用字母\(E\)表示隨機事件
事件的概念
-
基本事件(樣本點): \(\omega\) 進行一次隨機試驗會出現的結果,也就是試驗中不能再分解的結果。
-
樣本空間:\(\Omega\) 全體基本事件的集合。
-
隨機事件:試驗的每一個可能結果,是樣本空間的子集,也就是若干基本事件組成的集合。
事件及運算
-
事件發生:某個事件的任意基本事件發生。
-
必然事件:一定會發生的事件,可以理解成樣本空間。
-
事件發生:如果事件\(A\)發生必然導致事件\(B\)發生,則稱事件\(B\)包含\(A\) 也就是\(A\subset B\)。
-
事件的和:\(A+B\) \(A\)和\(B\)至少有一個發生,記作\(A \cup B\)。
-
事件的積:\(AB\) \(A\)和\(B\)同時發生,記作\(A \cap B\)。
-
事件的差:\(A-B\) \(A\)發生而\(B\)不發生,記作\(A-B\)。
-
互斥事件:\(A\)和\(B\)不同時發生,記作\(AB=\phi\)。
-
對立事件:\(A\)和\(B\)不同時發生且\(A \cup B=\Omega\),它們互為逆事件,記作\(A=\bar{B}\)或者\(B=\bar{A}\)。(類似補集)
概率的數學定義
設\(\omega\)為試驗\(E\)的樣本空間,並且每一個事件\(A_1,A_2...A_i\)都可以用一個實數\(P(A)\)來表示,那么滿足:
-
非負性:\(P(A)>=0\)
-
正則性:\(P(\Omega)=1\)
-
可列可加性:\(P(\bigcup_{i=1}^{\infty})=\sum_{i=1}^{\infty}P(A_i)\)
古典概率模型
- 所有可能出現的基本事件有有限個。
- 每個基本事件出現的可能性相等。
幾何概率
- 基本事件有無限個。
- 每個基本事件出現的可能性相等。
數學期望
定義
如果\(X\)是一個離散變量,它所有的輸出是\(x1,x2..xn\),每個輸出對應的概率是\(p1,p2..pn\),那么\(X\)的期望是\(E(X)=\sum_{i=1}^{n}x_i*p_i\)。
性質
-
對於任意隨機變量\(X,Y\)和常量\(a,b\),有\(E(aX+bY)=aE(X)+bE(Y)\)
-
對於任意隨機變量\(X,Y\)獨立並且各自有一個已經定義的期望,\(E(XY)=E(X)E(Y)\)。
例題
例\(1\) 綠豆蛙的歸宿
分析:期望入門題目,反正核心就應該是從終點到起點遞歸。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
int n,m,h[200005],cnt,r[200005],vis[200005];
struct node{int v,w,nxt;}e[200005];
double f[200005];
void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void dfs(int u)
{
if(u==n)
{
f[n]=0;
return;
}
if(vis[u])return;
vis[u]=1;
for(int i=h[u];i!=-1;i=e[i].nxt)
{
int v=e[i].v,w=e[i].w;
dfs(v);
f[u]+=(f[v]+w*1.0)/r[u]*1.0;
}
}
int main()
{
memset(h,-1,sizeof(h));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
r[u]++;
}
dfs(1);
printf("%.2lf\n",f[1]);
return 0;
}
例\(2\) [NOI2005]聰聰與可可
分析:仔細分析條件,我們可以得出:貓貓每次可以走一步或者兩步,費用算一步;老鼠每次要么走一步要么不走;貓貓每次要走離老鼠最近且編號最小的點。
我們發現這個距離似乎不好直接去求,所以可以運用\(SPFA\)預處理,\(d[i][j]\)表示當貓在i點老鼠在j點,之間的最小距離,\(catgo[i][j]\)表示貓在\(i\)點老鼠在\(j\)點,走一步到的最小點,於是有了以下預處理。
for(int i=1;i<=n;i++)
spfa(d[i],i);
for(int u=1;u<=n;u++)
for(int i=h[u];i!=-1;i=e[i].nxt)
{
int tmp=e[i].to;
for(int j=1;j<=n;j++)
if(d[u][j]-1==d[tmp][j])
catgo[u][j]=min(catgo[u][j],tmp);
}
然后我們就可以進行期望的計算了,如果設\(f[u][v]\)表示\(u\),\(v\)兩點之間的期望,那么就要從\(u\)走到\(v\),把路程中的期望都計算出來。
double dfs(int u,int v)
{
if(vis[u][v])return f[u][v];
if(u==v)return 0;\\相等則期望0
int fir=catgo[u][v];
int sec=catgo[fir][v];
if(fir==v||sec==v)return 1;\\如果走一步或者兩步就能達到目的 期望為1
f[u][v]=1;//期望步數從1開始 因為兩步也算一步的費用qwq
for(int i=h[v];i!=-1;i=e[i].nxt)\\因為是從u到v 所以枚舉v所有的路徑 繼續記搜
{
int vv=e[i].to;
f[u][v]+=dfs(sec,vv)/(c[v]+1)*1.0;
}
f[u][v]+=dfs(sec,v)/(c[v]+1)*1.0;\\不要忘記停留在原地的情況
vis[u][v]=true;\\記憶化
return f[u][v];
}
記憶化的核心在於,如果某一層遞歸訪問出現過,直接返回那個值就可以,不用往下搜索。
完整代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
using namespace std;
int n,m,cat,mouse,h[2005],vis[2005][2005],cnt,catgo[1005][1005],d[1005][1005],viss[2005],c[2005];
struct node
{
int to,nxt;
}e[2005];
double f[1005][1005];
queue<int>q;
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void spfa(int *d,int pos)
{
d[pos]=0;
q.push(pos);
// viss[pos]=true;
while(q.size())
{
int u=q.front();q.pop();
viss[u]=false;
for(int i=h[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(d[u]+1<d[v])
{
d[v]=d[u]+1;
if(!viss[v])viss[v]=true,q.push(v);
}
}
}
}
double dfs(int u,int v)
{
if(vis[u][v])return f[u][v];
if(u==v)return 0;
int fir=catgo[u][v];
int sec=catgo[fir][v];
if(fir==v||sec==v)return 1;
f[u][v]=1;//期望步數從1開始 因為兩步也算一步的費用qwq
for(int i=h[v];i!=-1;i=e[i].nxt)
{
int vv=e[i].to;
f[u][v]+=dfs(sec,vv)/(c[v]+1)*1.0;
}
f[u][v]+=dfs(sec,v)/(c[v]+1)*1.0;
vis[u][v]=true;
return f[u][v];
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d%d%d",&n,&m,&cat,&mouse);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
c[u]++,c[v]++;
}
// cout<<"qwq"<<endl;
memset(d,0x3f3f3f3f,sizeof d);
memset(catgo,0x3f3f3f3f,sizeof catgo);
for(int i=1;i<=n;i++)
spfa(d[i],i);
for(int u=1;u<=n;u++)
for(int i=h[u];i!=-1;i=e[i].nxt)
{
int tmp=e[i].to;
for(int j=1;j<=n;j++)
if(d[u][j]-1==d[tmp][j])
catgo[u][j]=min(catgo[u][j],tmp);
}
printf("%.3lf\n",dfs(cat,mouse));
return 0;
}
例3 [HNOI2013]游走
本題看似很難,找不到頭緒,其實有幾個關鍵點。
首先我們發現,求邊的期望似乎不可行,因為沒給出限制,數據太大會直接炸掉,所以我們可以把目光轉移到點上來。
關於點的期望,用\(f_i\)表示的話,那么:
\(f_i+=f_j/d_j+1\) \((i=1)\)
\(f_i+=f_j/d_j\) \((i!=1)\)
\(i\)和\(j\)是兩個相鄰的點,由於\(i\)點是由\(j\)點到達的,所以概率應該是\(\frac{1}{d_j}\)。
其次我們發現,每一條邊的期望值如果用\(g_i\)來表示,那么這條邊的期望值就可以表示為\(g_i=\frac{f_u}{d_u}+\frac{f_v}{d_v} (v,u!=n)\)。
最后,由於是求最小的期望,所以從小到大排個序,求出答案即可。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,cnt,h[500005],uu[500005],vv[500005],d[505];
double a[505][505],b[505],g[500005],f[500005];
struct node{int v,nxt;}e[500005];
void add(int u,int v)
{
e[++cnt].v=v;
e[cnt].nxt=h[u];
h[u]=cnt;
}
void Gauss(int n)
{
for(int i=1;i<=n;++i)
{
int p=i;
for(int k=i+1;k<=n;++k) if(fabs(a[k][i])>fabs(a[p][i])) p=k;
if(i!=p) swap(a[i],a[p]),swap(b[i],b[p]);
for(int k=i+1;k<=n;++k)
{
double d=a[k][i]/a[i][i];
b[k]-=d*b[i];
for(int j=i;j<=n;++j) a[k][j]-=d*a[i][j];
}
}
for(int i=n;i>=1;--i)
{
for(int j=i+1;j<=n;++j) b[i]-=f[j]*a[i][j];
f[i]=b[i]/a[i][i];
}
}
int main()
{
memset(h,-1,sizeof(h));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&uu[i],&vv[i]);
add(uu[i],vv[i]),add(vv[i],uu[i]);
++d[uu[i]],++d[vv[i]];
}
for(int u=1;u<n;u++)
{
a[u][u]=1.0;
for(int i=h[u];i!=-1;i=e[i].nxt)
{
int v=e[i].v;
if(v!=n)a[u][v]=-1.0/d[v];
}
}
b[1]=1;
Gauss(n-1);
for(int i=1;i<=m;i++)
{
int u=uu[i],v=vv[i];
if(u!=n)g[i]+=(f[u]/d[u]);
if(v!=n)g[i]+=(f[v]/d[v]);
}
sort(g+1,g+1+m);
double ans=0;
for(int i=1;i<=m;i++)
ans+=(m-i+1)*g[i];
printf("%.3lf\n",ans);
return 0;
}