好久没更新博客,来水一蛤,近三天做了二十道概率题,也算是摸到了一点门道。
首先关于一个很多博主的说到的点,也是我个人认为很重要的一个点,就是概率要顺着推,期望倒着推,我比较赞同以下这篇博客的观点:https://blog.csdn.net/nameofcsdn/article/details/52082746
其次就是关于概率DP状态的设计,概率DP的题往往设计状态比较直观,直接按照题意来就可以(也有可能是我做的题太水了)。关于状态的转移,其最重要的核心就是严格按照全概率公式和全期望公式来递推,要保证枚举出所有可能的状态,并保证他们是互斥的事件并且概率和为1.下面是几个简单的例题。
概率:
1,codeforces 148D Bag of mice
题目链接:https://vjudge.net/problem/CodeForces-148D
题意:袋子里有w只白鼠,b只黑鼠,P先抓,D后抓,并且D抓老鼠时会随机跑出来一只老鼠,谁抓到白鼠谁获胜。先求P获胜的概率。
设dp[i][j]为P抓老鼠时,袋子里还剩i只白鼠,j只黑鼠这种情况发生的概率,显然初始状态是w只白鼠,b只黑鼠,所以dp[w][b]=1,然后从该状态出发,由于我们设定的dp数组是P抓老鼠时所面临的情形,所以状态转移时要往前想两步。也就是dp[i][j]可能会由dp[i+1][j+2]和dp[i][j+3]两种状态转移过来,注意这里要想清楚,由于我们的dp数组保存的时这种情形发生的情况,所以中途是不能出现有人获胜的,也就是状态转移的过程中P,D都只能抓到黑鼠。这样就可以得出状态转移方程:
if (j+2<=b&&i+1<=w) dp[i][j]+=dp[i+1][j+2]*((j+2)*1.0/(i+j+3))*((j+1)*1.0/(i+j+2))*((i+1)*1.0/(i+j+1));
if (j+3<=b) dp[i][j]+=dp[i][j+3]*((j+3)*1.0/(i+j+3))*((j+2)*1.0/(i+j+2))*((j+1)*1.0/(i+j+1));
然后对于所有的情形,你只需要让P接来下抓到白鼠就可获胜,对这些概率求和即可。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<vector>
#include<map>
#include<bitset>
#include<algorithm>
using namespace std;
const long long MAXM=1e10+10;
const int INF=1e9;
int w,b;
double dp[1010][1010];
int main()
{
scanf("%d%d",&w,&b);
memset(dp,0,sizeof(dp));
dp[w][b]=1;
for (int i=w;i>=1;i--) {
for (int j=b;j>=0;j--) {
if (j+2<=b&&i+1<=w) dp[i][j]+=dp[i+1][j+2]*((j+2)*1.0/(i+j+3))*((j+1)*1.0/(i+j+2))*((i+1)*1.0/(i+j+1));
if (j+3<=b) dp[i][j]+=dp[i][j+3]*((j+3)*1.0/(i+j+3))*((j+2)*1.0/(i+j+2))*((j+1)*1.0/(i+j+1));
}
}
double ans=0;
for (int i=1;i<=w;i++) {
for (int j=0;j<=b;j++)
ans+=dp[i][j]*(i*1.0)/(i+j);
}
printf("%.9lf\n",ans);
return 0;
}
2,HDU 3366 Passage
题目链接:https://vjudge.net/problem/HDU-3366
题意:Bill面前有n扇门,身上有m块钱,Bill选择一扇门,Bill有p的概率走出去,q的概率遇到卫兵,如果遇到卫兵,你需要花费1块钱才能活着回到迷宫,然后剩下1-p-q的概率遇到死路从而回到迷宫。求在最优策略下逃出迷宫的概率。
关于这题的最优策略其实是我一直没有理解的点,个人觉得逃出迷宫的总概率应该是一个与策略无关的定值,但是却是对于样例2而言的话,两种顺序的结果不一样,如果有人可以解释清楚的话希望可以留下评论。在按照的所谓的最优策略下,也就是按照p/q排序,优先选择p/q较大的门。然后定义dp[i][j]为出现在第i扇门身上还有j块钱的情形出现的概率。在这里说一下,对于概率dp的题,我的状态定义方式与大多数博主都不一样,我看许多博客都是直接定义dp数组为某种情况下获胜的概率,但是我个人认为这种定义方式并不好理解(也许是我智商堪忧),所以我都是将dp数组定义为某种情形出现的概率,这样方便理解和推导状态的转移。
由于这样定义状态之后,所以进行转移时是不考虑Bill逃出的情况,也不考虑中间死亡的情况(因为一旦出现这些情况,接下来的情形是不会出现的),显然初始状态是0扇门身上有m块钱,dp[0][m]=1,状态转移方程如下。
dp[i][j]+=dp[i-1][j]*(1-r[i-1].p-r[i-1].q);
if (j+1<=m) dp[i][j]+=dp[i-1][j+1]*r[i-1].q;
得出所有情形的概率后,在每种情形下乘上在该种情形下逃出的概率,求和,即为逃出的总概率。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<stack>
#include<queue>
#include<set>
#include<vector>
#include<map>
#include<bitset>
#include<algorithm>
using namespace std;
const long long MAXM=1e5+10;
const int INF=1e9;
struct rd {
double p,q;
rd () {}
rd (double pp,double qq) {
p=pp; q=qq;
}
};
int n,m;
rd r[1010];
double dp[1010][20];
bool cmp(rd r1,rd r2) {
return (r1.p/r1.q)>(r2.p/r2.q);
}
int main()
{
int T;
scanf("%d",&T);
int cas=1;
while(T--) {
scanf("%d%d",&n,&m);
double x,y;
for (int i=1;i<=n;i++) {
scanf("%lf%lf",&x,&y);
rd r1(x,y); r[i]=r1;
}
sort(r+1,r+1+n,cmp);
memset(dp,0,sizeof(dp));
dp[0][m]=1;
for (int i=1;i<=n;i++) {
for (int j=m;j>=0;j--) {
dp[i][j]+=dp[i-1][j]*(1-r[i-1].p-r[i-1].q);
if (j+1<=m) dp[i][j]+=dp[i-1][j+1]*r[i-1].q;
}
}
double ans=0;
for (int i=1;i<=n;i++)
for (int j=0;j<=m;j++)
ans+=dp[i][j]*r[i].p;
printf("Case %d: ",cas++);
printf("%.5lf\n",ans);
}
return 0;
}
以上就是关于概率的内容,下篇博客会更新一些关于期望的题。