首页 > 代码库 > 概率类题目小练

概率类题目小练

 一般求概率的题目都是正着求,和求期望相反。但裸的求概率一般不多,很多都需要加以优化。最常见的有矩阵优化,记忆化搜索等,遇到有环的存在时需要用到高斯消元。

 

矩阵优化   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 }

 

概率类题目小练