概率是什么,好吃吗
简单的概率大家在小学时就已经接触过,一件事情\(A\)发生的概率为\(\frac{发生这件事的情况数}{总共的情况数}\)
用数学一点语言表示出来就是初中的知识了:如果样本空间由有限个等概率的简单事件组成,事件E的概率可以被表示为\(P(E)=\frac{|E|}{|S|}\)(S表示样本空间的总容量)
接下来介绍几个重要公式与定义
条件概率:我们用\(P(A|B)\)表示在事件B发生的前提下,事件A发生的概率;用\(P(AB)\)表示事件A和事件B同时发生的概率
则\(P(A|B)=\frac{P(AB)}{P(B)}\),注意:在事件AB互相独立的情况下,\(P(AB)=P(A)P(B)\)
其实在你不知道条件概率的时候,你已经很熟练的运用条件概率解决许多问题了,比如生日悖论
贝叶斯公式:\(P(A|B)=P(B|A)*P(A)/P(B)\)
全概率公式:假如我们将样本空间S分成若干个互不相交的部分\(B_1,B_2,\cdots,B_k\),则\(P(A)=P(A|B_1)*P(B_1)+P(A|B_2)*P(B_2)+\cdots+P(A|B_k)*P(B_k)\)
是不是感觉很复杂?其实你也在不知不觉中早就将其熟练运用了,比如(引用蓝书中的例子):ZZR参加某个比赛,获得一等奖、二等奖、三等奖、优胜奖的概率分别是0.1,0.2,0.3,0.4;在这四种情况下你被爸爸JJX表扬的概率分别是1.0,0.8,0.5,0.3,那么ZZR被表扬的概率就是\(0.1*1.0+0.2*0.8+0.3*0.5+0.4*0.3=0.45\)
注意:在使用全概率公式时不能将样本空间分割的有相交部分
数学期望:你可以将它理解成加权平均数,数学期望EX就是一个随机变量值的所有可能取值按照加权所得到的和,
举例子:假如一个随机变量有1/2的概率等于1,有1/3的概率等于2,有1/6的概率等于3,那么这个变量的取值的数学期望是\(1/2*1+1/3*2+1/6*3=5/3\)
再如:扔一个骰子,它取值的数学期望就是\(1/6*(1+2+3+4+5+6)=3.5\)
在一些考虑求数学期望的题目时,我们除了可以使用定义:计算出该变量所有可能的取值,再乘上它出现的概率;我们还可以使用如下的两个性质。
期望的线性性质:有限个随机变量之和的数学期望等于每个随机变量的数学期望之和,用式子表达就是\(E(X+Y)=E(X)+E(Y)\),更多的,对于常量\(a,b\)和变量\(X,Y\),有\(E(aX+bY)=aE(x)+bE(Y)\)
全期望公式:类似于全概率公式,同样是要注意将样本空间S分成若干个互不相交的部分,每一部分分开计算,注意不重不漏即可。下面也会给出全期望公式的数学表达式,但是样子十分丑陋,所以只给出其稍微简化的版本
\[E(Y)=E[E(Y|X)]=\sum_{x_i}P(X=x_i)E(Y|X=x_i)\]
其他有用的性质的还比如说概率均匀分布之类的在这里不再赘述,下面的题目若有提到再做说明(把它作为线段感性理解一下吧,深入研究的话很恶心)
例题
(主要来自UVA,如果不想去那个慢的要死的网站的话可以考虑luogu或者是vjudge)
1、UVA11021 Tribles
题意:一开始有k种生物,这种生物只能活1天,死的时候有\(p_i\)的概率产生i只这种生物(也只能活一天),询问m天内所有生物都死的概率(包括m天前死亡的情况)
分析:基础的求概率的题目,直接运用全概率公式即可
由于k种生物互相独立,我们可以求出一个生物在m天全部死亡的概率\(f(m)\),最后答案就是\(f(m)^k\)
考虑利用全概率递推:这个生物剩下了\(i\)个后代,在\(j\)天内全部死亡可以被表示成\(P_i*f(j)^i\)
那么就有\(f(i)=P_0+P_1*f(i-1)+P_2*f(i-1)^2+\cdots+P_n-1*f(i-1)^{n-1}\)
解释:这个生物在这一天可能不生下后代(\(P_0\)),也可能生下\(j\)个后代(\(P_j\)),它们在这一天之后的\(i-1\)天后全部死亡(\(f(i-1)\)),利用全概率公式计算即可
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
double p[1010],f[1010];
int n,m,k;
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
int t=read(),cas;
for (cas=1;cas<=t;cas++)
{
n=read();k=read();m=read();
int i,j;
for (i=0;i<n;i++) scanf("%lf",&p[i]);
f[0]=0.0;f[1]=p[0];
for (i=2;i<=m;i++)
{
f[i]=p[0];
for (j=1;j<n;j++) f[i]+=p[j]*pow(f[i-1],j);
}
printf("Case #%d: %0.7lf\n",cas,pow(f[m],k));
}
return 0;
}
2、[SHOI2002]百事世界杯之旅
很显然的结论是,假设当前你已经收集到了\(k\)个球星的名字,那么收集到第\(k+1\)个的概率是\(\frac{n-k}{n}\)
接下来就只要根据这个概率来算出数学期望就可以了,我们简记上面的概率为\(Q\)
如果你购买一次就收集到了一个新的,\(E=Q*1=Q\)
如果你购买两次才收集到了一个新的,\(E=2*Q(1-Q)\)
如果你购买三次才收集到了一个新的,\(E=3*Q(1-Q)^2\)
\(\cdots\)
如果我手气够背,买了\(n\)次才买到(假设\(n\)是一个很大的且不确定的数),那么此时的数学期望就是\(E=n*Q*(1-Q)^{n-1}\)
所以总的数学期望就是\(E=Q+2Q(1-Q)+3Q(1-Q)^2+\cdots+nQ(1-Q)^{n-1}\)
等等,我们的期望中怎么出现了一个不确定的数字?这让我们怎么计算啊QAQ
这是一个无穷级数(即上述式子有无穷多项),我们不能直接计算它,我们要考虑一些数学技巧
首先,我们在式子两边同时除以\(Q\)
\[E/Q=1+2(1-Q)+3(1-Q)^2+\cdots\]
接下来,我们在式子两边同时乘以\(1-Q\)
\[(1-Q)E/Q=(1-Q)+2(1-Q)^2+3(1-Q)^3+\cdots\]
用第一个式子减去第二个式子
\[[1-(1-Q)]E/Q=E=1+(1-Q)+(1-Q)^2+\cdots\]
我们发现,这就是一个等比数列求和的问题,再一次在式子两边同时乘上\((1-Q)\)
\[(1-Q)E=(1-Q)+(1-Q)^2+(1-Q)^3+\cdots\]
上式减下式得
\(E=\frac{1-(1-Q)^n}{Q}\),其中\((1-Q)^n\)表示的是上下两式中不同的那一项,但是我们又注意到一点,由于\(0\leq Q\leq 1\),且\(n\)足够大(趋近于无穷大),因此\((1-Q)^n\)d的值已经接近于0,可以忽略!因此我们最后得到计算\(E\)的式子
\[E=1/Q\]
此结论适用于所有的数学可表示为最初的式子的题目
所以接下来的答案就很清晰了,对于任意的\(k\),我们可以求出得到\(k+1\)个的数学期望就是概率的倒数,即\(\frac{n}{n-k}\)
由于\(0\leq k \leq n-1\),所以最终答案就是\(\frac{n}{1}+\frac{n}{2}+\cdots+\frac{n}{n}=n*\sum_{i=1}^n\frac{1}{i}\)
最后注意一下垃圾的输出即可
关于上面的证明其实还有一种更为简单的证明方法,但是由于这篇题解太长了于是放在下面
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
long long p=0,q=1;
int n;
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
long long gcd(long long x,long long y)
{
if (y==0) return x;else return gcd(y,x%y);
}
int calc(long long x)
{
int cnt=0;
while (x) {x/=10;cnt++;}
return cnt;
}
int main()
{
n=read();
int i;
for (i=1;i<=n;i++)
{
long long g=gcd(q,i),tmp1=q/g,tmp2=i/g;
p=p*tmp2+n*tmp1;
q=q*tmp2;
long long r=gcd(p,q);
p/=r;q/=r;
}
long long shang=p/q;
if (!(p%q)) printf("%lld",shang);
else
{
p=p%q;
int wei=calc(shang);
for (i=1;i<=wei;i++) printf(" ");
printf("%lld\n",p);
if (shang>0) printf("%lld",shang);
wei=calc(q);
for (i=1;i<=wei;i++) printf("-");printf("\n");
wei=calc(shang);
for (i=1;i<=wei;i++) printf(" ");
printf("%lld",q);
}
return 0;
}
3、UVA11427 Expect the Expected
题意:一个人打牌,每天晚上他可以打\(n\)次牌,他打每场牌的胜率是\(p\),每天晚上只有当他某一时刻的总胜率大于\(p\)时,他才会去睡觉,否则就一直打下去,如果他打满了\(n\)次牌但是他的胜率还是没有一次大于\(p\),那么他就会失去梦想从此放弃成为赌神的梦想。求他放弃梦想的期望天数
分析:概率dp
与第一题类似,在他没有放弃梦想的情况下,每一天的情况是独立的,于是我们可以只考虑一天的情况
由于直接求期望不好求,于是我们考虑通过求概率从而求期望,因为我们手推一下可以发现:在得知每一天失去梦想的概率后,我们可以用和T2一样的方法得出期望,在这里奉上一种更为简单的证明在无穷级数中概率和期望的关系
我们假设最后所求的期望是\(E\),我们把失去梦想的情况分为两种,一种是他在第一天就失去了梦想,那么此时的数学期望是\(Q*1=Q\),另一种是他在第一天还没有失去梦想,此时的概率为\(1-Q\),由于第一天对他后面的情况没有任何影响,即从第二天到最后和从第一天到最后这两种情况对答案不会有任何影响,因此在第二天到最后失去梦想的平均天数为\(E+1\)(原来的期望加上已经胜利的第一天),根据全期望公式可以列出方程:\(E=Q+(1-Q)(E+1)\),解得\(Q=1/E\)
那么接下来的问题就是求概率了,在不能直接求出概率的情况下,我们就会考虑递推或者dp,一般我们都是考虑dp
我们记\(dp[i][j]\)为这一天玩了\(i\)局游戏赢了\(j\)局的概率,为了使这个概率有意义,我们要保证\(j/i\leq p\)
转移利用全概率公式,考虑第\(j\)局是否胜利,即\(dp[i][j]=dp[i-1][j-1]*q+dp[i-1][j]*(1-q)\)
最后的我们要求的概率就是\(\sum_{i=1}^{i/n\leq p}dp[n][i]\)
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
double dp[110][110];
int a,b,n,t;
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
t=read();int cas;
for (cas=1;cas<=t;cas++)
{
scanf("%d/%d %d",&a,&b,&n);
double p=1.0*a/b;
int i,j;
memset(dp,0.0,sizeof(dp));
dp[0][0]=1.0;
for (i=1;i<=n;i++)
for (j=0;j*b<=a*i;j++)
dp[i][j]=dp[i-1][j-1]*p+dp[i-1][j]*(1-p);
double ansq=0.0;
for (i=0;i*b<=a*n;i++) ansq+=dp[n][i];
printf("Case #%d: %d\n",cas,(int)(1/ansq));
}
return 0;
}
4、NOIp2016D1T3换教室
有了T3的铺垫这一题,这一题就变得比较简单了
当第 i(1≤i≤n−1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室
由题目中的这句话我们知道,我们要预先处理出所有点之间的最短路从而方便计算,由于\(v\leq 300\)所以直接用floyd
接下来就是dp了
我们记\(dp[i][j][0/1]\)表示前\(i\)节课,申请换了\(j\)次,第\(i\)节课是否申请,首先要满足\(j\leq m\)
接下来考虑分别从\(dp[i-1][j][0/1]\)(当前未申请)或者是\(dp[i-1][j-1][0/1]\)(当前已申请)转移即可
式子好长这里就不详细说明了
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
using namespace std;
double p[3000],dp[3000][3000][2];
int c[3000],d[3000],dis[3000][3000],n,m,v,e;
double Min(double x,double y)
{
if (x<y) return x;else return y;
}
void init()
{
scanf("%d%d%d%d",&n,&m,&v,&e);
int i,j;
for (i=1;i<=n;i++) scanf("%d",&c[i]);
for (i=1;i<=n;i++) scanf("%d",&d[i]);
for (i=1;i<=n;i++) scanf("%lf",&p[i]);
for (i=1;i<=v;i++)
{
for (j=1;j<=v;j++)
if (i==j) dis[i][j]=0; else dis[i][j]=99999999;
}
for (i=1;i<=e;i++)
{
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
dis[a][b]=min(w,dis[a][b]);
dis[b][a]=min(w,dis[b][a]);
}
for (i=1;i<=n;i++)
for (j=0;j<=m;j++) dp[i][j][0]=dp[i][j][1]=99999999;
dp[1][0][0]=0;dp[1][1][1]=0;dp[0][0][0]=0;
}
void floyd()
{
int i,j,k;
for (k=1;k<=v;k++)
{
for (i=1;i<=v;i++)
{
for (j=1;j<=v;j++)
{
if (dis[i][j]>dis[i][k]+dis[k][j]) dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
void work()
{
int i,j;
for (i=2;i<=n;i++)
{
int tmp=min(i,m);
for (j=0;j<=tmp;j++)
{
dp[i][j][0]=Min(dp[i-1][j][0]+dis[c[i-1]][c[i]],dp[i-1][j][1]+dis[d[i-1]][c[i]]*p[i-1]+dis[c[i-1]][c[i]]*(1-p[i-1]));
if (j>0)
dp[i][j][1]=Min(dp[i-1][j-1][0]+dis[c[i-1]][d[i]]*p[i]+dis[c[i-1]][c[i]]*(1-p[i]),
dp[i-1][j-1][1]+dis[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+dis[c[i-1]][d[i]]*(1-p[i-1])*p[i]+dis[d[i-1]][c[i]]*p[i-1]*(1-p[i])+dis[d[i-1]][d[i]]*p[i-1]*p[i]);
}
}
}
void out()
{
double ans=99999999;int i;
for (i=0;i<=m;i++) ans=Min(ans,Min(dp[n][i][1],dp[n][i][0]));
printf("%.2lf",ans);
}
int main()
{
init();
floyd();
work();
out();
return 0;
}
5、UVA11762 Race to 1
题意:给定一个整数n,每次可以在不超过n的素数中选择一个数p,如果p是n的约数,就将n变成n/p,反之则n不改变,求将n变成1的期望次数
分析:题目给的数据都没什么涵养,我们来看一下n=6时的情况(从别人博客上粘贴的)
我们记\(dp[i]\)表示将i变成1的期望操作,那么\(dp[6]\)由全期望公式可以被表示成如下
\[dp[6]=1+\frac{1}{3}*dp[6]+\frac{1}{3}dp[3]+\frac{1}{3}dp[2]\]
其中右边的1表示已经进行了一次操作,而这一次操作分别有1/3的几率使6变成6,3,2,因此直接利用全期望公式计算即可
下面演示一下n=6时的计算
由上述知\(2dp[6]=3+dp[3]+dp[2]\)
先做简单的,\(dp[2]=1+dp[1]=1\)(2只能选择2,此时相除得到1)
接下来,\(dp[3]=1+\frac{1}{2}dp[3]+\frac{1}{2}dp[1]=1+\frac{1}{2}dp[3]\),最终解得\(dp[3]=2\)
因此\(dp[6]=\frac{1}{2}(3+dp[3]+dp[2])=3\)
大概由上面的推导过程,再结合一下全期望公式和线性性质,我们可以得到如下的转移
记\(p(x)\)为不大于x的素数的个数,\(g(x)\)表示有多少个不大于x的素数是它的因数,记为它们\(a_1.a_2,\cdots,a_{p(x)}\),则转移方程式
\[dp[x]=1+\sum_{i=1}^{p(x)}[dp[x/a_i]/p(x)]+dp[x]*(1-\frac{g(x)}{f(x)})\]
解释:对于当前的数x,我有\(\frac{g(x)}{f(x)}\)找到一个是x的因数的素数,剩下的概率就是找不到,同时再加上最开始的这一次转移
最后对这个式子进行化简可以得到(化简过程略,将带有\(dp[x]\)的项整理在一起,注意最后将sigma中的分母拆出来可以约分,同时不要忘记1)
\[dp[x]=\frac{\sum_{i=1}^{p(x)}dp[x/a_i]+p(x)}{g(x)}\]
边界情况\(dp[1]=0\),直接进行记忆化搜索即可
预处理直接用线筛找出小于\(10^6\)的素数
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
double dp[1000100];
int prime[500100],cnt=0;
bool vis[1001000],is_prime[1001000];
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
void init()
{
memset(is_prime,1,sizeof(is_prime));
is_prime[0]=is_prime[1]=0;
int i,j;
for (i=2;i<=1000000;i++)
{
if (is_prime[i]) prime[++cnt]=i;
for (j=1;j<=cnt && i*prime[j]<=1000000;j++)
{
is_prime[i*prime[j]]=0;
if (!(i%prime[j])) break;
}
}
}
double do_dp(int x)
{
if (vis[x]) return dp[x];
int p=0,g=0,i;
double ans=0.0;
for (i=1;i<=cnt && prime[i]<=x;i++)
{
p++;
if (!(x%prime[i]))
{
g++;
ans+=do_dp(x/prime[i]);
}
}
ans=(ans+p)/g;dp[x]=ans;
return dp[x];
}
int main()
{
init();
int t=read(),cas;
memset(vis,0,sizeof(vis));dp[1]=0.0;vis[1]=1;
for (cas=1;cas<=t;cas++)
{
int n=read();
do_dp(n);
printf("Case %d: %0.10lf\n",cas,dp[n]);
}
return 0;
}
6、UVA10900 So you want to be a 2n-aire?
最后用一道简单的古典概率题结束吧
题意:你一开始有1元钱,你可以回答n个问题,对于每个问题提出之前,你可以选择:1)放弃回答,拿走现在的所有奖金。2)回答该问题,正确奖金翻倍,错误奖金清零。假设你回答对每个问题的概率在\(t\)到1之间,求你获得奖金的期望最大值
分析:我们首先假设回答问题正确有一个固定概率\(p\),那么我们很容易写出如下的式子
记\(f[i]\)为答对i题获得奖金的期望最大值,则\(dp[i]=max(2^i,dp[i+1]*p)\)(分别考虑是否进行下一次答题)
那么我们可以考虑什么时候取这个最大值从而计算出期望。
我们考虑上面两个值的比值,我们记\(p_0=max(t,\frac{2^i}{dp[i+1]})\) (为了保证\(p\geq t\)故取max)
接下来我们进行分类讨论:
1)若\(dp[i]=2^i\),则\(2^i>dp[i+1]*p\),移项得\(p<p_0\)
2)若\(dp[i]=dp[i+1]*p\)那么同样由上面得\(p\leq p_0\)
然而我们知道p并非一个固定的值,它是在t和1之间的一个取值,接下来我们还要吧这一部分考虑进去来计算最终的期望,我们继续分类讨论
1)若\(p<p_0\),我们此时知道\(p \in [t,p_0)\),我们在数轴上表示出这两个值
上面的一根线表示p的取值,下面的表示t,我们可以这样考虑,我们有两条线段,长度分别是\(1-t\)和\(p_0-t\),第二条线段是第一条的一部分,那么我现在在第一条线段上随机取一个点,这个点落在第二条线段上的概率是多少?
很明显是第二条线段的长度和第一条的比值,记\(p_1=\frac{p_0-t}{1-t}\),那么这个就是取第一种情况的概率
2)若取\(dp[i+1]*p\),那么此时的第二条线段就是\(p_0到t\)的那一部分,此时的期望要怎么计算呢?要注意到p永远只是个变量,真正的不变量是\(p_0和t\),我们要考虑怎么用这两个值来表示期望
由于\(dp[i+1]\)也应该是个定值,所以我们在原来的计算期望公式中可以吧\(dp[i+1]\)作为一个公因数提出,那么就只剩下对概率做期望,很明显这时的概率期望就是p的一个平均值(通过图来理解这个“平均值”),即\((1+p_0)/2\)
最后综合起来,当前正确回答第i题的最大期望奖金可以被表示为\(2^i*p_1+dp[i+1]*(1+p_0)/2*(1-p_1)\)
从后往前递推,答案就是\(dp[0]\)
#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
double dp[40],t;
int n;
int read()
{
int x=0,f=1;char ch=getchar();
while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
return x*f;
}
int main()
{
while ((scanf("%d%lf",&n,&t)==2) && (n!=0))
{
if (t==1.0) {printf("0.3lf\n",(1<<n));continue;}
memset(dp,0,sizeof(dp));
dp[n]=(1<<n);int i;
for (i=n-1;i>=0;i--)
{
double p0=max(t,(double)(1<<i)/dp[i+1]);
double p1=(p0-t)/(1-t);
dp[i]=p1*(double)(1<<i)+(1+p0)/2.0*(1-p1)*dp[i+1];
}
printf("%0.3lf\n",dp[0]);
}
return 0;
}