一般求概率的题目都是正着求,和求期望相反。但裸的求概率一般不多,很多都需要加以优化。最常见的有矩阵优化,记忆化搜索等,遇到有环的存在时需要用到高斯消元。
矩阵优化 POJ 3744
题目链接:http://poj.org/problem?id=3744
题意:在一条不满地雷的路上,你现在的起点在1处。在N个点处布有地雷,1<=N<=10。地雷点的坐标范围:[1,100000000].每次前进p的概率前进一步,1-p的概率前进两步。问顺利通过这条路的概率。
很裸的状态转移,并用矩阵乘法分段优化,里面也存在环,但由于转移方程里面每一项都和dp[0]也就是答案有关,可以直接手动推最终公式。
设dp[i]表示到达i点的概率,则初始值dp[1]=1.
很容易想到转移方程:dp[i]=p*dp[i-1]+(1-p)*dp[i-2];
但是由于坐标的范围很大,直接这样求是不行的,而且当中的某些点还存在地雷。
N个有地雷的点的坐标为 x[1],x[2],x[3]```````x[N].
我们把道路分成N段:
1~x[1],x[1]+1~x[2]、、、x[N-1]+1~x[N].
这样每一段只有一个地雷。我们只要求得通过每一段的概率。乘法原理相乘就是答案。
对于每一段,通过该段的概率等于1-踩到该段终点的地雷的概率。
就比如第一段 1~x[1]. 通过该段其实就相当于是到达x[1]+1点。那么p[x[1]+1]=1-p[x[1]].
但是这个前提是p[1]=1,即起点的概率等于1.对于后面的段我们也是一样的假设,这样就乘起来就是答案了。
对于每一段的概率的求法可以通过矩阵乘法快速求出来。
1 //#include<bits/stdc++.h> 2 #include<stdio.h> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=2; 6 struct node 7 { 8 double f[maxn][maxn]; 9 }; 10 int num[15]; 11 node solve(node a,node b) 12 { 13 node ans; 14 for(int i=0;i<maxn;i++) 15 for(int j=0;j<maxn;j++) 16 { 17 ans.f[i][j]=0.0; 18 for(int k=0;k<maxn;k++) 19 ans.f[i][j]+=a.f[i][k]*b.f[k][j]; 20 } 21 return ans; 22 } 23 node power(node x,int y) 24 { 25 node ans={1,0,0,0}; 26 while(y) 27 { 28 if(y&1) 29 ans=solve(ans,x); 30 x=solve(x,x); 31 y/=2; 32 } 33 return ans; 34 } 35 int main() 36 { 37 int n; 38 double p; 39 while(~scanf("%d%lf",&n,&p)) 40 { 41 for(int i=1;i<=n;i++) 42 scanf("%d",&num[i]); 43 sort(num+1,num+1+n); 44 if(num[1]==1) 45 { 46 printf("0.0000000\n"); 47 continue; 48 } 49 node x{p,1,1.0-p,0}; 50 double ans=1.0; 51 for(int i=1;i<=n;i++) 52 { 53 if(num[i]==num[i-1]) continue; 54 node f=power(x,num[i]-num[i-1]-1); 55 ans*=1.0-f.f[0][0]; 56 } 57 printf("%.7f\n",ans); 58 } 59 return 0; 60 }
记忆化搜索 CF 148D
题目链接:http://codeforces.com/problemset/problem/148/D
袋子里有w只白鼠和b只黑鼠
龙和公主轮流从袋子里抓老鼠。谁先抓到白色老师谁就赢。
公主每次抓一只老鼠,龙每次抓完一只老鼠之后会有一只老鼠跑出来。
每次抓老鼠和跑出来的老鼠都是随机的。
如果两个人都没有抓到白色老鼠则龙赢。公主先抓。
问公主赢的概率。
存在用dfs搜索和for循环查询的方法,但for循环推公式比较麻烦,建议使用dfs
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1005; 4 double dp[maxn][maxn]; //用dp记录每种状态公主获胜的概率 5 int vis[maxn][maxn]; 6 double dfs(int x,int y) 7 { 8 if(x<=0) return 0; 9 if(y<=0) return 1; 10 if(vis[x][y]) return dp[x][y]; //避免重复的求概率,不然会超时 11 vis[x][y]=1; 12 double ans=1.0*x/(x+y); //公主直接摸到白球 13 if(y>=2) //若黑球小于两个,但前面保证有黑球,所以黑球被公主摸走,龙摸到白球,赢的概率为0 14 { 15 double k=1.0*y/(x+y); //公主摸一个黑球 16 y--; 17 k*=1.0*y/(x+y); //龙摸一个黑球 18 y--; 19 dp[x-1][y]=dfs(x-1,y); //白球少一个的情况 20 dp[x][y-1]=dfs(x,y-1); //黑球少一个的情况 21 ans+=k*(1.0*x/(x+y)*dp[x-1][y]+1.0*y/(x+y)*dp[x][y-1]); 22 } 23 return ans; 24 } 25 int main() 26 { 27 int a,b; 28 scanf("%d%d",&a,&b); 29 printf("%.9f\n",dfs(a,b)); 30 return 0; 31 }
二进制技巧 POJ 3071
题目链接:http://poj.org/problem?id=3071
2^n个队进行足球赛,每个队打败另外一个队都有一个概率。
问最后胜利的概率最大的是哪只球队。
注意:每个队只能和他旁边的队比赛,(1,2)(3,4)......
1 #include<stdio.h> 2 #include<algorithm> 3 using namespace std; 4 const int maxn=7; 5 const int maxm=(1<<maxn)+5; 6 double p[maxm][maxm],dp[maxn][maxm]; //dp[i][j]表示第i场比赛第j队获胜的概率 7 int main() 8 { 9 int n; 10 while(scanf("%d",&n),n!=-1) 11 { 12 int m=(1<<n); 13 //注意给队伍的编号是0~m-1,接下来会省事一点 14 for(int i=0; i<m; i++) 15 for(int j=0; j<m; j++) 16 scanf("%lf",&p[i][j]); 17 for(int i=0; i<m; i++) 18 dp[0][i]=1.0; //初始化还没开始比赛时每只队伍出线的概率都为1 19 for(int i=1; i<=n; i++) 20 for(int j=0; j<m; j++) 21 { 22 dp[i][j]=0; 23 int f=j/(1<<(i-1)); 24 f^=1; 25 for(int k=f*(1<<(i-1)); k<(f+1)*(1<<(i-1)); k++) 26 dp[i][j]+=dp[i-1][k]*dp[i-1][j]*p[j][k]; 27 } 28 int ans=0; 29 double sum=dp[n][0]; 30 for(int i=1; i<m; i++) 31 if(dp[n][i]>sum) 32 { 33 sum=dp[n][i]; 34 ans=i; 35 } 36 printf("%d\n",ans+1); 37 } 38 return 0; 39 }