概率期望初步
前言
概率期望是\(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;
}