概率與期望DP小結。
好久沒更新部落格,來水一蛤,近三天做了二十道概率題,也算是摸到了一點門道。
其次就是關於概率DP狀態的設計,概率DP的題往往設計狀態比較直觀,直接按照題意來就可以(也有可能是我做的題太水了)。關於狀態的轉移,其最重要的核心就是嚴格按照全概率公式和全期望公式來遞推,要保證枚舉出所有可能的狀態,並保證他們是互斥的事件並且概率和為1.下面是幾個簡單的例題。
概率:
1,codeforces 148D Bag of mice
題意:袋子裡有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
題意: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;
}
以上就是關於概率的內容,下篇部落格會更新一些關於期望的題。