浅谈概率期望动态规划
前言:自从去年noip有了换教室这道概率DP,
这以后各种OJ上的期望概率DP也越来越多,这里简单做个介绍
--------------------------------------------------------------------------------------------------
文章中涉及例题:hdu 4089,hdu 4405,hdu 4576,poj 2096,poj 3744
[ 1 ] hdu 4405 机器人
要点:基础的概率DP+逆推
题意:
多组输入n,m,l,r。表示在一个环上有n个格子
接下来输入m个w表示连续的一段命令,每个w表示机器人沿顺时针或者逆时针方向前进w格,
已知机器人是从1号点出发的,输出最后机器人停在环上[l,r]区间的概率。n(1 ≤ n ≤ 200),m(0 ≤ m ≤ 1,000,000)
分析:我们可以很直接的列出转移方程
第一种:f[i]=f[i-w]*0.5+f[i+w]*0.5
第二种:f[i-w]+=f[i]*0.5 , f[i+w]+=f[i]*0.5 (i-w和i+w位置根据模n来定)
因为大部分的概率期望DP都会运用到逆推的思想,因此我采用的是第一种转移方法
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 int n,m,x,y,to[101000]; 8 double f[100100]; 9 int main(){ 10 while (scanf("%d%d",&n,&m)==2&&!(n==0&&m==0)){ 11 memset(to,-1,sizeof(to)); 12 for (int i=1;i<=m;++i) 13 scanf("%d%d",&x,&y),to[x]=y; 14 memset(f,0,sizeof(f)); 15 for (int i=n-1;i>=0;--i) 16 if (to[i]!=-1) f[i]=f[to[i]]; 17 else for (int j=1;j<=6;++j) f[i]+=(f[i+j]+1)/6.0; 18 printf("%.4f\n",f[0]); 19 } 20 }
[ 2 ] poj 2096 收集漏洞
要点:逆推+推式子
题意:
输入n,s表示这里存在n种漏洞和s个系统(0<n,s<=1000)。
工程师可以花费一天去找出一个漏洞——这个漏洞可以是以前出现过的种类,也可能是未曾出现过的种类
同时,这个漏洞出现在每个系统的概率相同。要求得出找到n种漏洞,并且在每个系统中均发现漏洞的期望天数。
分析:
这道题目我们应把方程设为四个部分:
(1)漏洞未发现,系统未发现漏洞;(2)漏洞已发现,系统未发现漏洞;
(3)漏洞未发现,系统已发现漏洞;(4)漏洞已发现,系统已发现漏洞;
分别对应了(1)f[i+1][j+1] (2)f[i][j+1] (3)f[i+1][j] (4)f[i][j]
那么转移方程就可以轻松列出来了
如果我们设P1=(n-i)*(s-j)/n*s P2=(n-i)*j/n*s P3=i*(s-j)/n*s P4=i*j/n*s
则 f[i][j] = (f[i][j]+1) * P4 + (f[i][j+1]+1) * P3 + (f[i+1][j]+1) * P2 + (f[i+1][j+1]+1) * P1
因为我们发现左右都有f[i][j],那么我们将它化简
f[i][j] = (f[i][j+1] * P3 + f[i+1][j] * P2 + f[i+1][j+1] * P1 + 1) / (1-P4)
这样就可以逆推了
1 #include<iostream> 2 #include<cstring> 3 #include<cmath> 4 #include<cstdio> 5 #include<algorithm> 6 using namespace std; 7 double f[1010][1010]; 8 int n,s; 9 int main(){ 10 while (~scanf("%d%d",&n,&s)){ 11 f[n][s]=0; 12 for (int i=n;i>=0;--i) 13 for (int j=s;j>=0;--j) if (i!=n||j!=s) 14 f[i][j]=( 15 f[i+1][j]*(n-i)*j+ 16 f[i][j+1]*i*(s-j)+ 17 f[i+1][j+1]*(n-i)*(s-j)+n*s) 18 /(n*s-i*j)*1.0; 19 printf("%.4f\n",f[0][0]); 20 } 21 return 0; 22 }
[ 3 ] poj 3744 YYF侦查员
要点:矩阵快速幂
题意:
输入n表示共有n个地雷(0<n<=10),并且输入每个地雷所在的位置ai (ai为不大于108的正整数)。
现在求从1号位置出发越过所有地雷的概率。
用两种行走方式:①走一步②走两步(不会踩爆中间那个雷)。这两个行为的概率分别为p和(1-p)。
分析:
先列出转移方程 f[i]=f[i-1]*p+f[i-2]*(1-p),但是这个方程的前提是他不死
那么这个方程只适用于没有地雷的情况,如果有地雷就只能走两步,跨过地雷了
你可能会以为这样就结束了,那你naifu了,数据范围是1e8,一步一步枚举早已T飞了
那我们想想如何优化这个过程
看到这个式子有没有联想到~~,斐波那契数列,那么是不是还可以用矩阵快速幂了呢
很明显是的嘛,好于是我们就可以构造出一个矩阵然后随意乱搞就行了
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 int a[100100],n; 8 double p,ans; 9 struct matr{ 10 double f[3][3]; 11 void init1(){ 12 f[1][1]=p; f[1][2]=1-p; 13 f[2][1]=1; f[2][2]=0; 14 a[0]=0; ans=1; 15 } 16 void init2(){ 17 f[1][1]=f[2][2]=1; 18 f[1][2]=f[2][1]=0; 19 } 20 }t; 21 matr operator *(matr a,matr b){ 22 matr res; 23 for (int i=1;i<=2;++i) for (int j=1;j<=2;++j){ 24 res.f[i][j]=0; 25 for (int k=1;k<=2;++k) 26 res.f[i][j]+=a.f[i][k]*b.f[k][j]; 27 } return res; 28 } 29 void pow(matr a,int x){ 30 matr res; res.init2(); 31 while (x){if (x&1) res=res*a; a=a*a; x>>=1;} 32 ans*=(1-res.f[1][1]); 33 } 34 int main(){ 35 while (scanf("%d%lf",&n,&p)==2){ 36 for (int i=1;i<=n;++i) scanf("%d",&a[i]); 37 sort(a+1,a+n+1); t.init1(); 38 for (int i=1;i<=n;++i) 39 if (a[i]!=a[i-1]) pow(t,a[i]-a[i-1]-1); 40 printf("%.7f\n",ans); 41 } 42 return 0; 43 }
Summary:
总的来说概率期望DP只是一类问题,而他的实质是通过数学式子的推导,
再用一些简单的技巧来简化这个求解的过程,其中可能会有逆推,矩阵等等,
当然希望读者也可以在这基础上多加多加练习,祝各位NOIP RP++!!!