总结:概率和期望问题的求解都要依靠递推式,概率问题按照递推式进行dp,期望问题根据递推式化简的难易程度通过化简再dp或高斯消元求解,该类问题大部分仍属于各个类型的dp求解问题
2018.9.3-概率与期望dp学习
一排1到n的格子,每个格子上有黄金 ai ,你最开始在 1 号,每一次投骰子决定到哪一个格子,超出1~n范围则重新投掷,你到了哪个格子就得到哪个格子的金币,问最终在n 能得到金币的期望
#include<stdio.h>
#include<string.h>
const int MAX=1e2+5;
int a[MAX];
double dp[MAX];
int t,n;
int main()
{
scanf("%d",&t);
for(int tc=1;tc<=t;++tc)
{
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
dp[1]=1.0;
for(int i=1;i<n;++i)
{
for(int j=1;j<=6&&i+j<=n;++j)
{
int tmp=6;
if(i+6>n) tmp=n-i;
dp[i+j]+=dp[i]/tmp;
}
}
double ans=0;
for(int i=1;i<=n;++i) ans+=dp[i]*a[i];
printf("Case %d: %f\n",tc,ans);
}
return 0;
}
有n扇门和m枚金币,每扇门后有3种可能:1.出路概率p,2.另一扇门概率1-p-q,3.失去一枚金币并且出现另一扇门概率q,门可选择,求在最优选择情况下找到出路得概率
根据p/q从大到小对每扇门排序即为最优策略,注意q可能的值为0,需eps处理
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX=1e3+5;
const double eps=1e-16;
double dp[MAX][15],ans;
int t,n,m;
struct P
{
double p,q;
}a[MAX];
inline bool cmp(P& x,P& y)
{
return (x.p+eps)/(x.q+eps)>(y.p+eps)/(y.q+eps);
}
int main()
{
scanf("%d",&t);
for(int tc=1;tc<=t;++tc)
{
memset(dp,0,sizeof(dp));
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i) scanf("%lf%lf",&a[i].p,&a[i].q);
sort(a,a+n,cmp);
dp[0][m]=1.0;ans=0;
for(int i=0;i<n;++i)
{
for(int j=m;j>=0;--j)
{
ans+=dp[i][j]*a[i].p;
if(i<n-1&&j>=1)dp[i+1][j-1]+=dp[i][j]*a[i].q;
if(i<n-1)dp[i+1][j]+=dp[i][j]*(1.0-a[i].p-a[i].q);
}
}
printf("Case %d: %.5f\n",tc,ans);
}
return 0;
}
小岛上有man\tiger\deer三种生物,每天都有两种相遇:1.tiger-man 2.tiger-tiger 3.tiger-deer 4.man-deer/deer 5.deer-deer
求man活下来的概率,当且仅当tiger==0。:本题求的是man能活下来的最终概率而不是某种情况下仍存活的概率,题中3\4\5种情况只能延长man的存活时间,而不能影响man的存活概率,因此只需要对1\2情况进行讨论。
#include<stdio.h>
#include<string.h>
const int MAX=1e3+5;
double dp[MAX];
int t,n,m;
int main()
{
scanf("%d",&t);
for(int tc=1;tc<=t;++tc)
{
memset(dp,0,sizeof(dp));
scanf("%d%d",&n,&m);
if(n==0) {printf("Case %d: 1\n",tc);continue;}
if(n%2==1) {printf("Case %d: 0\n",tc);continue;}
dp[n]=1;
for(int i=n;i>=2;i-=2) dp[i-2]=dp[i]*(i-1)/(i+1);
printf("Case %d: %.6f\n",tc,dp[0]);
}
return 0;
}
有n只白鼠m只黑鼠,小D和小P二人随机各抓1只并且小D先手,小P抓完后会有1只小鼠出逃,求小D先抓到白鼠的概率
与上题相似,抓到白鼠则游戏结束,只需讨论2人都抓不到的概率和2种小鼠随机出逃的概率,答案为各种情况下抓到白鼠的概率之和: 本题我的解法中用eps忽略不会到达的情况,需要注意eps精度的设置,题中精度偏差不超过1e-9,eps=1e-11可AC
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX=1e3+1;
const double eps=1e-11;
double dp[MAX][MAX];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
memset(dp,0,sizeof(dp));
dp[n][m]=1;
double ans=0,tmp;
for(int i=n;i>0;--i)
{
for(int j=m;j>=0;--j)
{
if(dp[i][j]<eps) continue;
ans+=dp[i][j]*i/(i+j);
if(j>=2)tmp=dp[i][j]*j*(j-1)/(i+j)/(i+j-1);
if(i>=1&&j>=2)dp[i-1][j-2]+=tmp*i/(i+j-2);
if(j>=3)dp[i][j-3]+=tmp*(j-2)/(i+j-2);
}
}
printf("%.9f\n",ans);
return 0;
}
一维路径上有N个地雷,有2种情况:1.走一步概率p,2.走两步概率1-p,求从1走到MAX不踩到雷得概率(n<=10,MAX=1e8)
由于地雷少且路径长,考虑矩阵快速幂优化,区间状态转移矩阵为
把地雷看作区间间断点,假设 为间断点,走两步到达,进行区间概率统计即可
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[11],n;
double p,q;
struct Mat
{
double m[2][2];
Mat(){memset(m,0,sizeof(m));}
inline void init()
{
m[0][0]=0;m[0][1]=1;m[1][0]=q;m[1][1]=p;
}
};
inline Mat multi(const Mat &a,const Mat &b)
{
Mat c;
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
for(int k=0;k<2;++k)
c.m[i][j]+=a.m[i][k]*b.m[k][j];
return c;
}
Mat pow(Mat &a,int k)
{
Mat b;
for(int i=0;i<2;++i) b.m[i][i]=1;
while(k)
{
if(k&1) b=multi(b,a);
a=multi(a,a);
k>>=1;
}
return b;
}
double ans;
int main()
{
while(~scanf("%d%lf",&n,&p))
{
q=1.0-p;
for(int i=0;i<n;++i) scanf("%d",&a[i]);
sort(a,a+n);
n=unique(a,a+n)-a;
if(a[0]==1) {printf("%.7f\n",0.0);continue;}
Mat m1;m1.init();
ans=pow(m1,a[0]-1).m[0][1]*q;
for(int i=0;i<n-1;++i)
{
m1.init();
ans=ans*pow(m1,a[i+1]-a[i]-1).m[0][1]*q;
}
printf("%.7f\n",ans);
}
return 0;
}
每次投1个球投不中概率为p,连中n球或连失m球则停止,求投球次数的期望
关键词:马尔可夫链、正向推概率反向推期望
设,,
1.
2.
3.
由23得 ,,代入1
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
const int MAX=51;
const double eps=1e-8;
int t,n,m;
double p,q;
int main()
{
scanf("%d",&t);
for(int tc=1;tc<=t;++tc)
{
scanf("%lf%d%d",&p,&n,&m);
q=1.0-p;
if(p<eps||q<eps) {printf("Case %d: %.8f\n",tc,p<eps?n*1.0:m*1.0);continue;}
double a=1.0-pow(q,n-1),b=1.0-pow(p,m-1);
double e10=(a*b/q+a/p)/(1.0-a*b),e01=(a*b/p+b/q)/(1.0-a*b);
printf("Case %d: %.8f\n",tc,e10*q+e01*p+1.0);
}
return 0;
}
有三个骰子,分别有K1,K2,K3个面,点数都在[1,K1]\[1,K2]\[1,K3]之间,同时掷出三个骰子:若点数分别为a,b,c则sum=0;否则sum+=点数之和,当sum>n结束,求掷骰子次数的期望
同上,概率公式中有环,每一个都可以转化到,不妨设代入迭代化简得
问题化简为求和
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX=5e2+1;
const double eps=1e-8;
double p[MAX],pa[MAX],pb[MAX],p0;
int t,n,K1,K2,K3,a,b,c;
int main()
{
scanf("%d",&t);
while(t--)
{
memset(p,0,sizeof(p));
memset(pa,0,sizeof(pa));
memset(pb,0,sizeof(pb));
scanf("%d%d%d%d%d%d%d",&n,&K1,&K2,&K3,&a,&b,&c);
p0=1.0/K1/K2/K3;
for(int i=1;i<=K1;++i)
for(int j=1;j<=K2;++j)
for(int k=1;k<=K3;++k)
p[i+j+k]+=p0;
p[a+b+c]-=p0;
for(int i=n;i>=0;--i)
{
pa[i]+=p0;
pb[i]+=1;
for(int j=3;j<=K1+K2+K3&&i+j<=n;++j)
{
pa[i]+=pa[i+j]*p[j];
pb[i]+=pb[i+j]*p[j];
}
}
printf("%.8f\n",pb[0]/(1.0-pa[0]));
}
return 0;
}
买魔法士干脆面收集袋中卡牌,共有n张卡牌,袋中有i卡牌得概率为pi,,求买干脆面的期望
n张卡牌形成1<<n种状态,考虑通过状态压缩列状态转移方程:
,
设 , ,化简得
根据递推式反向求期望
#include<bits/stdc++.h>
using namespace std;
const int MAX=1<<20|1;
double dp[MAX],p[21],ps,p1,p2;
int n;
int main()
{
while(~scanf("%d",&n))
{
memset(dp,0,sizeof(dp));ps=1.0;
for(int i=0;i<n;++i) {scanf("%lf",&p[i]);ps-=p[i];}
for(int i=(1<<n)-2;i>=0;--i)
{
p1=p2=0;
for(int j=0;j<n;++j)
{
if(i&1<<j) p1+=p[j];
else p2+=dp[i|1<<j]*p[j];
}
p2+=1.0;
dp[i]=p2/(1.0-p1-ps);//1<<n-1,1.0-p1-ps==0
}
printf("%.4f\n",dp[0]);
}
return 0;
}
高斯消元
有两个账号,每次选取一个分数最低的账号进行游戏,有p得概率得1分,1-p得概率失2分,最低0分,求1个号到达20的期望
由题意得,两个号的分差必定不超过1,当一个号为20分时,另一个为19
按递推式建立矩阵,套用高斯消元函数,得到解集
表示从i分到20分的期望,所以答案即为:两倍的0~20减去19~20
该题的递推式也可以通过换元进行化简
#include<bits/stdc++.h>
using namespace std;
const int MAX=2e2+5;
const double eps=1e-11;
double a[MAX][MAX],x[MAX],p,q;
int equ,var,n=20;
bool gauss()
{
int i,j,k,col,max_r;
for(k=0,col=0;k<equ&&col<var;++k,++col)
{
max_r=k;
for(i=k+1;i<equ;++i)
if(fabs(a[i][col])>fabs(a[max_r][col]))
max_r=i;
if(fabs(a[max_r][col])<eps) return 0;
if(k!=max_r)
{
for(j=col;j<var;++j) swap(a[k][j],a[max_r][j]);
swap(x[k],x[max_r]);
}
x[k]/=a[k][col];
for(j=col+1;j<var;++j) a[k][j]/=a[k][col];
a[k][col]=1.0;
for(i=0;i<equ;++i)
if(i!=k)
{
x[i]-=x[k]*a[i][col];
for(j=col+1;j<var;++j) a[i][j]-=a[k][j]*a[i][col];
a[i][col]=0.0;
}
}
return 1;
}
int main()
{
equ=var=n;
while(~scanf("%lf",&p))
{
memset(a,0,sizeof(a));
q=1.0-p;
a[0][0]=p;a[0][1]=-p;a[0][n]=x[0]=1.0;
a[1][0]=-q;a[1][1]=1.0;a[1][2]=-p;a[1][n]=x[1]=1.0;
for(int i=2;i<n;++i)
{
a[i][i-2]=-q;
a[i][i]=1.0;
a[i][i+1]=-p;
a[i][n]=x[i]=1.0;
}
gauss();
printf("%.6f\n",x[0]*2-x[19]);
}
return 0;
}
对离散型随机变量x,其概率为p,有
对随机变量A、B,有
第二条式子是今天的主角,他表明了期望有线性的性质,简单理解就是期望之间可根据关系,简单运算(不严谨的理解)。 这就为我们解决一个期望问题,不断转化为解决另外的期望问题,最终转化到一个已知的期望上。
举一个求期望最简单的例子,见下图。
假设有个人在 1号节点处,每一分钟他会缘着边随机走到一个节点或者在原地停留,问他走到4号节点需要平均几分钟?
这是个简单的期望问题,我们用Ei(i=1,2,3,4) 表示从i号节点走到4号节点的数学期望值。根据题意对1号节点有
E1=(1/3)*E1+(1/3)*E2+(1/3)*E3+1 ①
表示 他下一分钟可以走到2或者3或在原地1,每个可能概率是1/3 ,注意是下一分钟,故要加上1.
同理我们对节点2,3同样可以列出
E2=(1/3)*E1+(1/3)*E2+(1/3)*E4+1 ②
E3=(1/3)*E1+(1/3)*E3+(1/3)*E4+1 ③
那E4等于多少呢? 很明显E4=0 ④,因为他就是要到点4
这样上面1234式其实就是组成了一组方程组,解方程组就可得出E1!!,用高斯消元,复杂度是O(n^3)
从上述例子,我们可总结出如何解决期望类问题,根据题意,表示出各个状态的期望(上例的Ei,1234),根据概率公式,列出期望之间的方程,解方程即可。
下面看用上述思路如何解决一道题(poj2096)
题意简述: 一个人受雇于某公司要找出某个软件的bugs和subcomponents,这个软件一共有n个bugs和s个subcomponents,每次他都能同时随机发现1个bug和1个subcomponent,问他找到所有的bugs和subcomponents的期望次数。
我们用E(i,j)表示他找到了i个bugs和j个subcomponents,离找到n个bugs和s个subcomponents还需要的期望次数,这样要求的就是E(0,0),而E(n,s)=0,对任意的E(i,j),1次查找4种情况,没发现任何新的bugs和subcomponents,发现一个新的bug,发现一个新的subcomponent,同时发现一个新的bug和subcomponent,用概率公式可得:
E(i,j)=1+(i*j/n/s)*E(i,j)+(i*(s-j)/n/s)E(i,j+1)+((n-i)*j/n/s)*E(i+1,j)+(n-i)*(s-j)/n/s*E(i+1,j+1);
这样根据边界就可解出所有的E(i,j),注意因为当我们找到n个bugs和s个subcomponents就结束,对i>n||j>s均无解的情况,并非期望是0.(数学上常见问题,0和不存在的区别)
那这题是否也是要用高斯消元呢? 用高斯消元得话复杂度是O(n^3),达到10^18 根本是不可解的!!
但其实,注意观察方程,当我们要解E(i,j)的话就需要E(i+1,j),E(I,j+1),E(i+1,j+1), 一开始已知E(n,s),那其实只要我们从高往低一个个解出I,j就可以了! 即可根据递推式解出所有的E(I,j) 复杂度是O(n),10^6 ,完美解决。
从上面这道题,我们再次看到了解决期望问题的思路,而且是用到了递推解决问题,其实可递推的原因,当我们把各个状态当成是一个个节点时,概率关系为有向边,我们可看到,可递推的问题其实就是这个关系图是无环的!!那必须要用方程组解决的问题其实就是存在环!!!! 而且我还要指出的是用高斯消元的时候,要注意误差的问题,最好把式子适当的增大,避免解小数,否则误差太大,估计也会卡题。