1. 程式人生 > >概率與期望DP小結。

概率與期望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;
}

以上就是關於概率的內容,下篇部落格會更新一些關於期望的題。