【原創】概率DP總結 by kuangbin


概率DP主要用於求解期望、概率等題目。

轉移方程有時候比較靈活。

一般求概率是正推,求期望是逆推。通過題目可以體會到這點。

 

首先先推薦幾篇參考的論文:

《信息學競賽中概率問題求解初探》

《淺析競賽中一類數學期望問題的解決方法》

《有關概率和期望問題的研究

 

1、POJ 3744 

Scout YYF I
此題是一個用矩陣優化的求概率的題目。
主要思想是分段,根據轉移方程用矩陣求解。
題解見 here
POJ 3744
/*
POJ 3744

C++  0ms 184K
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
using namespace std;

struct Matrix
{
    double mat[2][2];
};
Matrix mul(Matrix a,Matrix b)
{
    Matrix ret;
    for(int i=0;i<2;i++)
      for(int j=0;j<2;j++)
      {
          ret.mat[i][j]=0;
          for(int k=0;k<2;k++)
            ret.mat[i][j]+=a.mat[i][k]*b.mat[k][j];
      }
    return ret;
}
Matrix pow_M(Matrix a,int n)
{
    Matrix ret;
    memset(ret.mat,0,sizeof(ret.mat));
    for(int i=0;i<2;i++)ret.mat[i][i]=1;
    Matrix temp=a;
    while(n)
    {
        if(n&1)ret=mul(ret,temp);
        temp=mul(temp,temp);
        n>>=1;
    }
    return ret;
}

int x[30];
int main()
{
    int n;
    double p;
    while(scanf("%d%lf",&n,&p)!=EOF)//POJ上G++要改為cin輸入
    {
        for(int i=0;i<n;i++)
          scanf("%d",&x[i]);
        sort(x,x+n);
        double ans=1;
        Matrix tt;
        tt.mat[0][0]=p;
        tt.mat[0][1]=1-p;
        tt.mat[1][0]=1;
        tt.mat[1][1]=0;
        Matrix temp;

        temp=pow_M(tt,x[0]-1);
        ans*=(1-temp.mat[0][0]);

        for(int i=1;i<n;i++)
        {
            if(x[i]==x[i-1])continue;
            temp=pow_M(tt,x[i]-x[i-1]-1);
            ans*=(1-temp.mat[0][0]);
        }
        printf("%.7lf\n",ans);//POJ上G++要改為%.7f
    }
    return 0;
}

 

 2、POJ 2096 Collecting Bugs

dp求期望,概率dp入門題。很簡單,題解見here

POJ 2096
/*
POJ 2096
概率DP
writed by kuangbin

dp求期望
逆着遞推求解
題意:(題意看題目確實比較難道,n和s都要找半天才能找到)
   一個軟件有s個子系統,會產生n種bug
   某人一天發現一個bug,這個bug屬於一個子系統,屬於一個分類
   每個bug屬於某個子系統的概率是1/s,屬於某種分類的概率是1/n
   問發現n種bug,每個子系統都發現bug的天數的期望。

求解:
         dp[i][j]表示已經找到i種bug,j個系統的bug,達到目標狀態的天數的期望
         dp[n][s]=0;要求的答案是dp[0][0];
         dp[i][j]可以轉化成以下四種狀態:
              dp[i][j],發現一個bug屬於已經有的i個分類和j個系統。概率為(i/n)*(j/s);
              dp[i][j+1],發現一個bug屬於已有的分類,不屬於已有的系統.概率為 (i/n)*(1-j/s);
              dp[i+1][j],發現一個bug屬於已有的系統,不屬於已有的分類,概率為 (1-i/n)*(j/s);
              dp[i+1][j+1],發現一個bug不屬於已有的系統,不屬於已有的分類,概率為 (1-i/n)*(1-j/s);
        整理便得到轉移方程
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int MAXN=1010;
double dp[MAXN][MAXN];

int main()
{
    int n,s;
    while(scanf("%d%d",&n,&s)!=EOF)
    {
        dp[n][s]=0;
        for(int i=n;i>=0;i--)
          for(int j=s;j>=0;j--)
          {
              if(i==n&&j==s)continue;
              dp[i][j]=(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j);
          }
        printf("%.4lf\n",dp[0][0]);//POJ上G++要改成%.4f
    }
    return 0;
}

 

 3、ZOJ 3329  One Person Game

此題的遞推方程稍微復雜點,需要轉化后求解系數。

題意:有三個骰子,分別有k1,k2,k3個面。
每次擲骰子,如果三個面分別為a,b,c則分數置0,否則加上三個骰子的分數之和。
當分數大於n時結束。求游戲的期望步數。初始分數為0

題解見here

 

ZOJ 3329
/*
ZOJ 3329
題意:有三個骰子,分別有k1,k2,k3個面。
每次擲骰子,如果三個面分別為a,b,c則分數置0,否則加上三個骰子的分數之和。
當分數大於n時結束。求游戲的期望步數。初始分數為0

設dp[i]表示達到i分時到達目標狀態的期望,pk為投擲k分的概率,p0為回到0的概率
則dp[i]=∑(pk*dp[i+k])+dp[0]*p0+1;
都和dp[0]有關系,而且dp[0]就是我們所求,為常數
設dp[i]=A[i]*dp[0]+B[i];
代入上述方程右邊得到:
dp[i]=∑(pk*A[i+k]*dp[0]+pk*B[i+k])+dp[0]*p0+1
     =(∑(pk*A[i+k])+p0)dp[0]+∑(pk*B[i+k])+1;
     明顯A[i]=(∑(pk*A[i+k])+p0)
     B[i]=∑(pk*B[i+k])+1
     先遞推求得A[0]和B[0].
     那么  dp[0]=B[0]/(1-A[0]);
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;

double A[600],B[600];
double p[100];
int main()
{
    int T;
    int k1,k2,k3,a,b,c;
    int n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
        double p0=1.0/k1/k2/k3;
        memset(p,0,sizeof(p));
        for(int i=1;i<=k1;i++)
          for(int j=1;j<=k2;j++)
            for(int k=1;k<=k3;k++)
              if(i!=a||j!=b||k!=c)
                p[i+j+k]+=p0;
        memset(A,0,sizeof(A));
        memset(B,0,sizeof(B));
        for(int i=n;i>=0;i--)
        {
            A[i]=p0;B[i]=1;
            for(int j=1;j<=k1+k2+k3;j++)
            {
                A[i]+=A[i+j]*p[j];
                B[i]+=B[i+j]*p[j];
            }
        }
        printf("%.16lf\n",B[0]/(1-A[0]));
    }
    return 0;
}

 

4、HDU  4405 Aeroplane chess

這題是2012年網絡賽的題目。是很簡單的概率dp.轉移方程很好想到。

求期望。按照公式從后望前遞推就可以得到答案了。

解題報告here

HDU 4405
/*

概率DP求期望。
形成一個有向無環圖。按照公式遞推就可以了。
dp[i]表示i點跳到目標狀態的期望步數

*/


#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;

const int MAXN=100010;
double dp[MAXN];
vector<int>vec[MAXN];
bool used[MAXN];
int main()
{
    int n,m;
    int u,v;
    while(scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)break;
        for(int i=0;i<=n;i++)vec[i].clear();
        memset(dp,0,sizeof(dp));
        while(m--)
        {
            scanf("%d%d",&u,&v);
            vec[v].push_back(u);
        }
        memset(used,false,sizeof(used));
        for(int i=0;i<vec[n].size();i++)
        {
            v=vec[n][i];
            dp[v]=0;
            used[v]=true;
        }
        for(int i=n-1;i>=0;i--)
        {
            if(used[i]==false)
            {
                for(int j=i+1;j<=i+6;j++)dp[i]+=dp[j]/6;
                dp[i]+=1;
                used[i]=true;
            }

            for(int j=0;j<vec[i].size();j++)
            {
                v=vec[i][j];
                dp[v]=dp[i];
                used[v]=true;
            }
        }
        printf("%.4lf\n",dp[0]);
    }
    return 0;
}

 

 5、HDU 4089 Activation

這題是求概率,但是也有種求期望的感覺,都是要列出公式來,化簡,遞推出答案。

2011年北京現場賽的題目。再比賽時做出來確實不容易,需要對概率DP很熟悉才能做出來。

解題報告見here

HDU 4089
/*
HDU 4098
題意:有n個人排隊等着在官網上激活游戲。Tomato排在第m個。
對於隊列中的第一個人。有一下情況:
1、激活失敗,留在隊列中等待下一次激活(概率為p1)
2、失去連接,出隊列,然后排在隊列的最后(概率為p2)
3、激活成功,離開隊列(概率為p3)
4、服務器癱瘓,服務器停止激活,所有人都無法激活了。
求服務器癱瘓時Tomato在隊列中的位置<=k的概率

解析:
概率DP;
設dp[i][j]表示i個人排隊,Tomato排在第j個位置,達到目標狀態的概率(j<=i)
dp[n][m]就是所求
j==1:    dp[i][1]=p1*dp[i][1]+p2*dp[i][i]+p4;
2<=j<=k: dp[i][j]=p1*dp[i][j]+p2*dp[i][j-1]+p3*dp[i-1][j-1]+p4;
k<j<=i:  dp[i][j]=p1*dp[i][j]+p2*dp[i][j-1]+p3*dp[i-1][j-1];
化簡:
j==1:    dp[i][1]=p*dp[i][i]+p41;
2<=j<=k: dp[i][j]=p*dp[i][j-1]+p31*dp[i-1][j-1]+p41;
k<j<=i:  dp[i][j]=p*dp[i][j-1]+p31*dp[i-1][j-1];

其中:
p=p2/(1-p1);
p31=p3/(1-p1)
p41=p4/(1-p1)

可以循環i=1->n 遞推求解dp[i].在求解dp[i]的時候dp[i-1]就相當於常數了。
在求解dp[i][1~i]時等到下列i個方程
j==1:   dp[i][1]=p*dp[i][i]+c[1];
2<=j<=k:dp[i][j]=p*dp[i][j-1]+c[j];
k<j=i:  dp[i][j]=p*dp[i][j]+c[j];
其中c[j]都是常數了。上述方程可以解出dp[i]了。
首先是迭代得到 dp[i][i].然后再代入就可以得到所有的dp[i]了。

注意特判一種情況。就是p4<eps時候,就不會崩潰了,應該直接輸出0
*/
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<algorithm>
#include<string.h>
using namespace std;

const int MAXN=2020;
const double eps=1e-5;
double c[MAXN];
double pp[MAXN];
double dp[MAXN][MAXN];
int main()
{
    int n,m,k;
    double p1,p2,p3,p4;
    while(scanf("%d%d%d%lf%lf%lf%lf",&n,&m,&k,&p1,&p2,&p3,&p4)!=EOF)
    {
        if(p4<eps)
        {
            printf("0.00000\n");
            continue;
        }
        double p=p2/(1-p1);
        double p41=p4/(1-p1);
        double p31=p3/(1-p1);
        pp[0]=1.0;//pp[i]=p^1;
        for(int i=1;i<=n;i++) pp[i]=p*pp[i-1];

        dp[1][1]=p41/(1-p);
        c[1]=p41;
        for(int i=2;i<=n;i++)
        {
            for(int j=2;j<=k;j++)c[j]=p31*dp[i-1][j-1]+p41;
            for(int j=k+1;j<=i;j++) c[j]=p31*dp[i-1][j-1];
            double tmp=c[1]*pp[i-1];
            for(int j=2;j<=k;j++)tmp+=c[j]*pp[i-j];
            for(int j=k+1;j<=i;j++)tmp+=c[j]*pp[i-j];
            dp[i][i]=tmp/(1-pp[i]);
            dp[i][1]=p*dp[i][i]+c[1];
            for(int j=2;j<i;j++)dp[i][j]=p*dp[i][j-1]+c[j];
        }
        printf("%.5lf\n",dp[n][m]);
    }
    return 0;
}

 

6、HDU 4035 Maze

經典的的概率DP的題目。做了可以體會到dp 求期望的一類的方法。

解題報告見here

HDU 4035
/*
HDU 4035
kuangbin
http://www.cnblogs.com/kuangbin/


    dp求期望的題。
    題意:
    有n個房間,由n-1條隧道連通起來,實際上就形成了一棵樹,
    從結點1出發,開始走,在每個結點i都有3種可能:
        1.被殺死,回到結點1處(概率為ki)
        2.找到出口,走出迷宮 (概率為ei)
        3.和該點相連有m條邊,隨機走一條
    求:走出迷宮所要走的邊數的期望值。

    設 E[i]表示在結點i處,要走出迷宮所要走的邊數的期望。E[1]即為所求。

    葉子結點:
    E[i] = ki*E[1] + ei*0 + (1-ki-ei)*(E[father[i]] + 1);
         = ki*E[1] + (1-ki-ei)*E[father[i]] + (1-ki-ei);

    非葉子結點:(m為與結點相連的邊數)
    E[i] = ki*E[1] + ei*0 + (1-ki-ei)/m*( E[father[i]]+1 + ∑( E[child[i]]+1 ) );
         = ki*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei)/m*∑(E[child[i]]) + (1-ki-ei);

    設對每個結點:E[i] = Ai*E[1] + Bi*E[father[i]] + Ci;

    對於非葉子結點i,設j為i的孩子結點,則
    ∑(E[child[i]]) = ∑E[j]
                   = ∑(Aj*E[1] + Bj*E[father[j]] + Cj)
                   = ∑(Aj*E[1] + Bj*E[i] + Cj)
    帶入上面的式子得
    (1 - (1-ki-ei)/m*∑Bj)*E[i] = (ki+(1-ki-ei)/m*∑Aj)*E[1] + (1-ki-ei)/m*E[father[i]] + (1-ki-ei) + (1-ki-ei)/m*∑Cj;
    由此可得
    Ai =        (ki+(1-ki-ei)/m*∑Aj)   / (1 - (1-ki-ei)/m*∑Bj);
    Bi =        (1-ki-ei)/m            / (1 - (1-ki-ei)/m*∑Bj);
    Ci = ( (1-ki-ei)+(1-ki-ei)/m*∑Cj ) / (1 - (1-ki-ei)/m*∑Bj);

    對於葉子結點
    Ai = ki;
    Bi = 1 - ki - ei;
    Ci = 1 - ki - ei;

    從葉子結點開始,直到算出 A1,B1,C1;

    E[1] = A1*E[1] + B1*0 + C1;
    所以
    E[1] = C1 / (1 - A1);
    若 A1趨近於1則無解...

*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
#include<vector>
using namespace std;
const int MAXN=10010;
const double eps=1e-9;//這里1e-8會WA。設為1e-9和1e-10可以
double k[MAXN],e[MAXN];
double A[MAXN],B[MAXN],C[MAXN];

vector<int>vec[MAXN];//存樹

bool dfs(int t,int pre)//t的根結點是pre
{
    int m=vec[t].size();//點t的度
    A[t]=k[t];
    B[t]=(1-k[t]-e[t])/m;
    C[t]=1-k[t]-e[t];
    double tmp=0;
    for(int i=0;i<m;i++)
    {
        int v=vec[t][i];
        if(v==pre)continue;
        if(!dfs(v,t))return false;
        A[t]+=(1-k[t]-e[t])/m*A[v];
        C[t]+=(1-k[t]-e[t])/m*C[v];
        tmp+=(1-k[t]-e[t])/m*B[v];
    }
    if(fabs(tmp-1)<eps)return false;
    A[t]/=(1-tmp);
    B[t]/=(1-tmp);
    C[t]/=(1-tmp);
    return true;
}
int main()
{
   // freopen("in.txt","r",stdin);
   // freopen("out.txt","w",stdout);
    int T;
    int n;
    int u,v;
    int iCase=0;
    scanf("%d",&T);
    while(T--)
    {
        iCase++;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)vec[i].clear();
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            vec[u].push_back(v);
            vec[v].push_back(u);
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%lf%lf",&k[i],&e[i]);
            k[i]/=100;
            e[i]/=100;
        }
        printf("Case %d: ",iCase);
        if(dfs(1,-1)&&fabs(1-A[1])>eps)
        {
            printf("%.6lf\n",C[1]/(1-A[1]));
        }
        else printf("impossible\n");
    }
}

 

 7、HDU 3853 LOOPS

 比較簡單的概率DP了,入門基礎題。

注意一個小陷進。

解題報告here

HDU 3853
/*
HDU 3853

解析:
設dp[i][j]表示(i,j)到(R,C)需要消耗的能量
則:
dp[i][j]=p1[i][j]*dp[i][j]+p2[i][j]*dp[i][j+1]+p3[i][j]*dp[i+1][j]+2;
化簡得到:
dp[i][j]=p2[i][j]*dp[i][j+1]/(1-p1[i][j])+p3[i][j]*dp[i+1][j]/(1-p1[i][j])+2/(1-p1[i][j]);
注意一種情況就是p1[i][j]==1的情況。
題目只是保證答案小於1000000.但是有的點可能永遠都不可能到達的。
所以這樣的點出現p1[i][j]是允許的。
否則就會WA了。
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int MAXN=1010;
const double eps=1e-5;
double dp[MAXN][MAXN];
double p1[MAXN][MAXN];
double p2[MAXN][MAXN];
double p3[MAXN][MAXN];

int main()
{
    int R,C;
    while(scanf("%d%d",&R,&C)!=EOF)
    {
        for(int i=1;i<=R;i++)
          for(int j=1;j<=C;j++)
            scanf("%lf%lf%lf",&p1[i][j],&p2[i][j],&p3[i][j]);
        dp[R][C]=0;
        for(int i=R;i>=1;i--)
          for(int j=C;j>=1;j--)
          {
              if(i==R&&j==C)continue;
              if(fabs(1-p1[i][j])<eps)continue;
              dp[i][j]=p2[i][j]/(1-p1[i][j])*dp[i][j+1]+p3[i][j]/(1-p1[i][j])*dp[i+1][j]+2/(1-p1[i][j]);
          }
        printf("%.3lf\n",dp[1][1]);
    }
    return 0;
}

 

8、POJ 2151 Check the difficulty of problems

此題還不算是概率DP的題目。就是DP題,求概率。

想到轉移方程就不難了。

題解見here

POJ 2151
/*
POJ 2151
題意:
ACM比賽中,共M道題,T個隊,pij表示第i隊解出第j題的概率
問 每隊至少解出一題且冠軍隊至少解出N道題的概率。

解析:DP
設dp[i][j][k]表示第i個隊在前j道題中解出k道的概率
則:
dp[i][j][k]=dp[i][j-1][k-1]*p[j][k]+dp[i][j-1][k]*(1-p[j][k]);
先初始化算出dp[i][0][0]和dp[i][j][0];
設s[i][k]表示第i隊做出的題小於等於k的概率
則s[i][k]=dp[i][M][0]+dp[i][M][1]+``````+dp[i][M][k];

則每個隊至少做出一道題概率為P1=(1-s[1][0])*(1-s[2][0])*```(1-s[T][0]);
每個隊做出的題數都在1~N-1的概率為P2=(s[1][N-1]-s[1][0])*(s[2][N-1]-s[2][0])*```(s[T][N-1]-s[T][0]);

最后的答案就是P1-P2
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
using namespace std;

double dp[1010][50][50];
double s[1010][50];
double p[1010][50];
int main()
{
    int M,N,T;
    while(scanf("%d%d%d",&M,&T,&N)!=EOF)
    {
        if(M==0&&T==0&&N==0)break;
        for(int i=1;i<=T;i++)
          for(int j=1;j<=M;j++)
           scanf("%lf",&p[i][j]);
        for(int i=1;i<=T;i++)
        {
            dp[i][0][0]=1;
            for(int j=1;j<=M;j++)dp[i][j][0]=dp[i][j-1][0]*(1-p[i][j]);

            for(int j=1;j<=M;j++)
              for(int k=1;k<=j;k++)
                dp[i][j][k]=dp[i][j-1][k-1]*p[i][j]+dp[i][j-1][k]*(1-p[i][j]);

            s[i][0]=dp[i][M][0];
            for(int k=1;k<=M;k++)s[i][k]=s[i][k-1]+dp[i][M][k];
        }
        double P1=1;
        double P2=1;
        for(int i=1;i<=T;i++)
        {
            P1*=(1-s[i][0]);
            P2*=(s[i][N-1]-s[i][0]);
        }
        printf("%.3lf\n",P1-P2);
    }
    return 0;
}

 

 9、Codeforces  148D Bag of mice

抓老鼠問題。轉移方程要思考下就出來了。

解題報告here

CF 148D
/*
CF 148D
題意:
原來袋子里有w只白鼠和b只黑鼠
龍和王妃輪流從袋子里抓老鼠。誰先抓到白色老師誰就贏。
王妃每次抓一只老鼠,龍每次抓完一只老鼠之后會有一只老鼠跑出來。
每次抓老鼠和跑出來的老鼠都是隨機的。
如果兩個人都沒有抓到白色老鼠則龍贏。王妃先抓。
問王妃贏的概率。

解析:
設dp[i][j]表示現在輪到王妃抓時有i只白鼠,j只黑鼠,王妃贏的概率
明顯 dp[0][j]=0,0<=j<=b;因為沒有白色老鼠了
      dp[i][0]=1,1<=i<=w;因為都是白色老鼠,抓一次肯定贏了。
      dp[i][j]可以轉化成下列四種狀態:
      1、王妃抓到一只白鼠,則王妃贏了,概率為i/(i+j);
      2、王妃抓到一只黑鼠,龍抓到一只白色,則王妃輸了,概率為j/(i+j)*i/(i+j-1).
      3、王妃抓到一只黑鼠,龍抓到一只黑鼠,跑出來一只黑鼠,則轉移到dp[i][j-3]。
      概率為j/(i+j)*(j-1)/(i+j-1)*(j-2)/(i+j-2);
      4、王妃抓到一只黑鼠,龍抓到一只黑鼠,跑出來一只白鼠,則轉移到dp[i-1][j-2].
      概率為j/(i+j)*(j-1)/(i+j-1)*i/(i+j-2);

      當然后面兩種情況要保證合法,即第三種情況要至少3只黑鼠,第四種情況要至少2只白鼠
*/

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int MAXN=1010;
double dp[MAXN][MAXN];
int main()
{
    int w,b;
    while(scanf("%d%d",&w,&b)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=b;i++)dp[0][i]=0;
        for(int i=1;i<=w;i++)dp[i][0]=1;
        for(int i=1;i<=w;i++)
          for(int j=1;j<=b;j++)
          {
              dp[i][j]+=(double)i/(i+j);//直接抓到白的
              if(j>=3)//抓到黑的,另外一個人也是抓到黑的,跑出一個黑的
                dp[i][j]+=(double)j/(i+j)*((double)(j-1)/(i+j-1))*((double)(j-2)/(i+j-2))*dp[i][j-3];
              if(j>=2) //抓到黑的,另外一個人也是抓到黑的,跑出一個白的
                dp[i][j]+=((double)j/(i+j))*((double)(j-1)/(i+j-1))*((double)i/(i+j-2))*dp[i-1][j-2];
          }
        printf("%.9lf\n",dp[w][b]);
    }
    return 0;
}

 

10、POJ 3071 Football

足球賽的淘汰賽問題。問最后勝利的概率最大的球隊。

簡單概率DP

題解報告見here

POJ 3071
/*
POJ 3071
題意:2^n個隊進行足球賽,每個隊打敗另外一個隊都有一個概率。
問最后勝利的概率最大的是哪只球隊。

概率公式,dp算一下就可以了。
*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;

double dp[8][200];//dp[i][j]表示在第i場比賽中j勝出的概率
double p[200][200];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        if(n==-1)break;
        memset(dp,0,sizeof(dp));
        for(int i=0;i<(1<<n);i++)
          for(int j=0;j<(1<<n);j++)
            scanf("%lf",&p[i][j]);
            //cin>>p[i][j];
        for(int i=0;i<(1<<n);i++)dp[0][i]=1;
        for(int i=1;i<=n;i++)//2^n個人要進行n場比賽
        {
            for(int j=0;j<(1<<n);j++)
            {
                int t=j/(1<<(i-1));
                t^=1;
                dp[i][j]=0;
                for(int k=t*(1<<(i-1));k<t*(1<<(i-1))+(1<<(i-1));k++)
                  dp[i][j]+=dp[i-1][j]*dp[i-1][k]*p[j][k];
            }
        }
        int ans;
        double temp=0;
        for(int i=0;i<(1<<n);i++)
        {
            if(dp[n][i]>temp)
            {
                ans=i;
                temp=dp[n][i];
            }
        }
        printf("%d\n",ans+1);
    }
    return 0;
}

 

 11、SGU  495 Kids and Prizes 

簡單的概率DP。  O(1),推公式就可以出來。

解題報告here

SGU 495
/*
SGU 495
題意:n個盒子里裝有禮物,m個人隨機選擇禮物,選完之后空盒子放回
問選中的禮物數的期望。

m個人是獨立的。
對於每個禮物不被人選中的概率為((n-1)/n)^m
那么不被選中的禮物數的期望就是 n*((n-1)/n)^m
所以答案就是  n-n*((n-1)/n)^m;

*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        double p=(double)(n-1)/n;
        double ans=n-n*pow(p,m);
        printf("%.10lf\n",ans);
    }
    return 0;
}

 

 12、ZOJ  3380 Patchouli's Spell Cards

用JAVA的大樹做的概率DP。有m個位置,每個位置填入1~n中的一個數,求至少有L個數一樣的概率。

解題報告見here

ZOJ 3380
/*
 * ZOJ 3380
 * 題目意思:有m個位置,每個位置填入一個數,數的范圍是1~n,問至少有L個位置的數一樣的概率
 * 輸出要是最簡分數的形式,所以用大數JAVA
 * 至少有L個位置一樣,就是L,L+1,L+2····m個位置一樣。
 * 我們從反面來考慮,總數是n^m,我們求沒有L個位置一樣的數的概率
 * 設 dp[i][j]表示用前i個數,填充j個位置的方案數(要符合沒有L個位置是一樣的數)
 * dp[i][j]=dp[i-1][j]+Sigm( dp[i-1][j-k]*C[m-(j-k)][k]  ) k<=j&&k<L
 * 其實就是看第i個數,可以不填,填一個位置,兩個位置······這樣累加過來。
 * 那么最后的答案就是 (n^m-dp[1~n][m])/(n^m)
 */
import java.util.*;
import java.io.*;
import java.math.*;
public class Main
{
    static BigInteger[][] dp=new  BigInteger[110][110];
    static BigInteger[][] C=new BigInteger[110][110];//組合數
    public static void main(String arg[])
    {
        Scanner cin=new Scanner(new BufferedInputStream(System.in));
        for(int i=0;i<105;i++)
        {
            C[i][0]=C[i][i]=BigInteger.ONE;
            for(int j=1;j<i;j++)
                C[i][j]=C[i-1][j-1].add(C[i-1][j]);
        }
        int N,M,L;
        while(cin.hasNext())
        {
            M=cin.nextInt();
            N=cin.nextInt();
            L=cin.nextInt();
            BigInteger tol=BigInteger.valueOf(N).pow(M);
            if(L>M)
            {
                System.out.println("mukyu~");
                continue;
            }
            if(L>M/2)//這個時候可以直接用組合數求出來
            {
                BigInteger ans=BigInteger.ZERO;
                for(int i=L;i<=M;i++)
                    ans=ans.add(C[M][i].multiply(BigInteger.valueOf(N-1).pow(M-i)));
                ans=ans.multiply(BigInteger.valueOf(N));
                BigInteger gcd=ans.gcd(tol);
                System.out.println(ans.divide(gcd)+"/"+tol.divide(gcd));
                continue;
            }
            for(int i=0;i<=N;i++)
                 for(int j=0;j<=M;j++)
                 {
                     dp[i][j]=BigInteger.ZERO;
                 }
            dp[0][0]=BigInteger.ONE;
            for(int i=1;i<=N;i++)
                for(int j=1;j<=M;j++)
                {
                    for(int k=0;k<L&&k<=j;k++)
                        dp[i][j]=dp[i][j].add(dp[i-1][j-k].multiply(C[M-(j-k)][k]));
                }
           BigInteger ans=BigInteger.ZERO;
           for(int i=1;i<=N;i++)
               ans=ans.add(dp[i][M]);    
           ans=tol.subtract(ans);
           BigInteger gcd=ans.gcd(tol);
           System.out.println(ans.divide(gcd)+"/"+tol.divide(gcd));
        }
    }
}

 

 13、ZOJ 3640  Help Me Escape

比較簡單的概率DP,記憶化搜索很好理解,也很容易寫。

解題報告here

ZOJ 3640
/*
題意:
一只吸血鬼,有n條路給他走,每次他隨機走一條路,
每條路有個限制,如果當時這個吸血鬼的攻擊力大於
等於某個值,那么就會花費t天逃出去,否則,花費1天
的時間,並且攻擊力增加,問他逃出去的期望

記憶化搜索做
*/

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
const int MAXN=200010;

double dp[MAXN];

int c[110];
int n;

double solve(int f)
{
    if(dp[f]>0)return dp[f];

    dp[f]=0;
    for(int i=0;i<n;i++)
    {
        if(f>c[i])
        {
            double temp=(1.0+sqrt(5))/2*c[i]*c[i];
            int t=(int)temp;
            dp[f]+=(double)t/n;
        }
        else
        {
            dp[f]+=(1+solve(f+c[i]))/n;
        }
    }
    return dp[f];
}
int main()
{
    int f;
    while(scanf("%d%d",&n,&f)!=EOF)
    {
        for(int i=0;i<n;i++)scanf("%d",&c[i]);
        memset(dp,0,sizeof(dp));
        printf("%.3lf\n",solve(f));
    }
    return 0;
}

 

 14、HDU 4336 Card Collector

求期望,可以狀態壓縮概率DP求解。也可以用容斥原理直接求。解題報告here

HDU 4336
/*
HDU 4336
題意:
有N(1<=N<=20)張卡片,每包中含有這些卡片的概率為p1,p2,````pN.
每包至多一張卡片,可能沒有卡片。
求需要買多少包才能拿到所以的N張卡片,求次數的期望。


可以用容斥原理做。也可以狀態壓縮進行概率DP
期望DP
*/
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
const int MAXN=22;
double p[MAXN];
double dp[1<<MAXN];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        double tt=0;
        for(int i=0;i<n;i++)
        {
            scanf("%lf",&p[i]);
            tt+=p[i];
        }
        tt=1-tt;//tt就表示沒有卡片的概率了
        dp[(1<<n)-1]=0;
        for(int i=(1<<n)-2;i>=0;i--)
        {
            double x=0,sum=1;
            for(int j=0;j<n;j++)
            {
                if((i&(1<<j)))x+=p[j];
                else sum+=p[j]*dp[i|(1<<j)];
            }
            dp[i]=sum/(1-tt-x);
        }
        printf("%.5lf\n",dp[0]);

    }
    return 0;
}
HDU 4336
/*
HDU 4336
容斥原理
位元素枚舉
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;

double p[22];
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        for(int i=0;i<n;i++)scanf("%lf",&p[i]);
        double ans=0;
        for(int i=1;i<(1<<n);i++)
        {
            int cnt=0;
            double sum=0;
            for(int j=0;j<n;j++)
              if(i&(1<<j))
              {
                  sum+=p[j];
                  cnt++;
              }
            if(cnt&1)ans+=1.0/sum;
            else ans-=1.0/sum;
        }
        printf("%.5lf\n",ans);
    }
    return 0;
}

 

 

下面介紹的三題是用高斯消元法求解的概率DP

15、ZJUT 1423 地下迷宮

 一個N*M的迷宮,除了障礙外等概率走,求起點走到終點步數的期望。先在起點進行bfs,找出所以可以到達的點並編號,然后建立方程組求解。

ZJUT 1423
/*

地下迷宮

Description:
由於山體滑坡,DK被困在了地下蜘蛛王國迷宮。為了搶在DH之前來
到TFT,DK必須盡快走出此迷宮。此迷宮僅有一個出口,而由於大BOSS
的力量減弱影響到了DK,使DK的記憶力嚴重下降,他甚至無法記得他
上一步做了什么。所以他只能每次等概率隨機的選取一個方向走。
當然他不會選取周圍有障礙的地方走。如DK周圍只有兩處空地,則每
個都有1/2的概率。現在要求他平均要走多少步可以走出此迷宮。

Input:
先是一行兩個整數N, M(1<=N, M<=10)表示迷宮為N*M大小,
然后是N行,每行M個字符,'.'表示是空地,'E’表示出口,'D’表示DK,'X’表示障礙。
Output:
如果DK無法走出或要超過1000000步才能走出,輸出tragedy!,
否則輸出一個實數表示平均情況下DK要走幾步可以走出迷宮,四舍五入到小數點后兩位。
Sample Input:
1 2
ED
3 3
D.X
.X.
X.E
Sample Output:
1.00
tragedy!


首先對地圖節點重新標號。假設E[i]表示DK從i點開始走出迷宮的期望值。
那么E[i]=(E[a1]+E[a2]+E[a3]+...+E[an])/n+1,其中a1...an是i的相鄰節點。
那么對於每一個DK可達的節點來說,都可以為它建立這樣的一個方程。現
在假設DK可達的點有N個,那么我們最終將會得到N元一次方程組。最后
利用高斯消元解出E[No[S]]。其中S是DK的起點,No[S]是重標號后的起點
這里要重點注意的是,我們聯立方程的時候,一定要注意DK可達這個條件,
不然就會導致無解的情況。

*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<math.h>
using namespace std;

#define eps 1e-9
const int MAXN=200;
double a[MAXN][MAXN],x[MAXN];//方程的左邊的矩陣和等式右邊的值,求解之后x存的就是結果
int equ,var;//方程數和未知數個數

int Gauss()
{
    int i,j,k,col,max_r;
    for(k=0,col=0;k<equ&&col<var;k++,col++)
    {
        max_r=k;
        for(i=k+1;i<equ;i++)
          if(fabs(a[i][col])>fabs(a[max_r][col]))
            max_r=i;
        if(fabs(a[max_r][col])<eps)return 0;
        if(k!=max_r)
        {
            for(j=col;j<var;j++)
              swap(a[k][j],a[max_r][j]);
            swap(x[k],x[max_r]);
        }
        x[k]/=a[k][col];
        for(j=col+1;j<var;j++)a[k][j]/=a[k][col];
        a[k][col]=1;
        for(i=0;i<equ;i++)
          if(i!=k)
          {
              x[i]-=x[k]*a[i][k];
              for(j=col+1;j<var;j++)a[i][j]-=a[k][j]*a[i][col];
              a[i][col]=0;
          }
    }
    return 1;
}

char map[20][20];
int num[20][20];
struct Node
{
    int x,y;
};
int sx,sy,ex,ey;
int n,m;
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int cnt;

void bfs()
{
    memset(num,-1,sizeof(num));
    cnt=0;
    num[sx][sy]=cnt++;
    queue<Node>que;
    Node temp;
    Node tt;
    temp.x=sx;temp.y=sy;
    que.push(temp);
    while(!que.empty())
    {
        temp=que.front();
        que.pop();
        for(int i=0;i<4;i++)
        {
            tt.x=temp.x+dir[i][0];
            tt.y=temp.y+dir[i][1];
            if(tt.x>=0&&tt.x<n&&tt.y>=0&&tt.y<m&&map[tt.x][tt.y]!='X'&&num[tt.x][tt.y]==-1)
            {
                num[tt.x][tt.y]=cnt++;
                que.push(tt);
            }
        }
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%s",&map[i]);
            for(int j=0;j<m;j++)
            {
                if(map[i][j]=='D')
                {
                    sx=i;sy=j;
                }
                if(map[i][j]=='E')
                {
                    ex=i;ey=j;
                }
            }
        }
        bfs();
        if(num[ex][ey]==-1)
        {
            printf("tragedy!\n");
            continue;
        }
        memset(a,0,sizeof(a));
        memset(x,0,sizeof(x));
        equ=var=cnt;
        for(int i=0;i<n;i++)
         for(int j=0;j<m;j++)
           if(num[i][j]!=-1)
           {
               int now=num[i][j];
               if(map[i][j]=='E')
               {
                   memset(a[now],0,sizeof(a[now]));
                   x[now]=0;
                   a[now][now]=1;
                   continue;
               }

               int Count=0;
               for(int k=0;k<4;k++)
               {
                   int tx=i+dir[k][0];
                   int ty=j+dir[k][1];
                   if(tx>=0&&tx<n&&ty>=0&&ty<m&&map[tx][ty]!='X'&&num[tx][ty]!=-1)
                   {
                       a[now][num[tx][ty]]=-1;
                       Count++;
                   }
                   a[now][now]=Count;
                   x[now]=Count;
               }
           }
        if(Gauss())
        {
            if(x[num[sx][sy]]<=1000000)printf("%.2lf\n",x[num[sx][sy]]);
            else printf("tragedy!\n");
        }
        else printf("tragedy!\n");
    }
    return 0;
}

 

16、ZJUT 1317 擲飛盤

在一個環上拋擲兩個飛盤 ,每個飛盤等概率往左和右走,問兩個飛盤走到同一個地方所需要步數的期望。

按照他們的距離表示狀態進行概率DP。dp[i]=dp[i-2]/4+dp[i+2]/4+dp[i]/2+1.整理下就出來方程。注意是循環的,要進行處理。

ZJUT 1317
/*
ZJUT 1317
擲飛盤

Description:
m個人位於正m邊形的頂點上,彼此拋擲飛盤。他們共有兩個飛盤,
且開始時這兩個飛盤位於相距為n的兩個人的手中(相鄰兩個人
相距為1,依此類推)。在每次拋擲時兩個飛盤被同時拋出,飛盤都
以1/2的概率被拋到擲飛盤的人左邊相鄰的人,1/2的概率被拋到
右邊相鄰的人。此過程一直進行,直到兩個飛盤被擲到同一個人
手中,求此拋擲飛盤的游戲平均情況下(期望)會在拋擲幾次后結束。

Input:
每行有兩個整數m (2<m<=100),n (0 < n < m)。
Output:
對每組數據m,n,輸出平均所需步數(四舍五入,保留兩位小數),
如果有限步內不可能結束就輸出INF。

Sample Input:
3 1
4 1
Sample Output:
4.00
INF

*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<math.h>
using namespace std;

#define eps 1e-9
const int MAXN=200;
double a[MAXN][MAXN],x[MAXN];//方程的左邊的矩陣和等式右邊的值,求解之后x存的就是結果
int equ,var;//方程數和未知數個數

int Gauss()
{
    int i,j,k,col,max_r;
    for(k=0,col=0;k<equ&&col<var;k++,col++)
    {
        max_r=k;
        for(i=k+1;i<equ;i++)
          if(fabs(a[i][col])>fabs(a[max_r][col]))
            max_r=i;
        if(fabs(a[max_r][col])<eps)return 0;
        if(k!=max_r)
        {
            for(j=col;j<var;j++)
              swap(a[k][j],a[max_r][j]);
            swap(x[k],x[max_r]);
        }
        x[k]/=a[k][col];
        for(j=col+1;j<var;j++)a[k][j]/=a[k][col];
        a[k][col]=1;
        for(i=0;i<equ;i++)
          if(i!=k)
          {
              x[i]-=x[k]*a[i][k];
              for(j=col+1;j<var;j++)a[i][j]-=a[k][j]*a[i][col];
              a[i][col]=0;
          }
    }
    return 1;
}

int n,m;
int num[MAXN];
int cnt;
int getnum(int x)
{
    x=(x%n+n)%n;
    if(x>n-x)x=n-x;
    return x;
}
void dfs(int x)
{
    x=getnum(x);
    num[x]=cnt++;
    int y=getnum(x+2);
    if(num[y]==-1)dfs(y);
    y=getnum(x-2);
    if(num[y]==-1)dfs(y);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(num,-1,sizeof(num));
        cnt=0;
        m=getnum(m);
        dfs(m);
        if(num[0]==-1)
        {
            printf("INF\n");
            continue;
        }
        memset(a,0,sizeof(a));
        memset(x,0,sizeof(x));
        for(int i=0;i<n;i++)
          if(num[i]!=-1)
          {
              int now=num[i];
              a[now][now]=2;
              x[now]=4;
              int t=getnum(i-2);
              a[now][num[t]]-=1;//這里一定要注意的,要減1,不能直接賦值為-1,
              t=getnum(i+2);//因為i-2和i+2是可能一樣的
              a[now][num[t]]-=1;
          }
        int t=num[0];
        memset(a[t],0,sizeof(a[t]));
        a[t][t]=1;
        x[t]=0;
        equ=var=cnt;//這個不要忘記了,經常忘掉!!!
        if(Gauss())printf("%.2lf\n",x[num[m]]);
        else printf("INF\n");
    }
    return 0;
}

 

 

17、HDU 4418 Time travel

在坐標軸上用高斯消元法求解。注意N=1的時候要特判一下。解題報告here

HDU 4418
/*
HDU 4118
題目:給出一個數軸,有一個起點和一個終點,某個人可以
走1,2,3……m步,每一種情況有一個概率,初始有一個方向,
走到頭則返回,問到達終點的期望步數為多少。

比較明顯的高斯求期望問題

Sample Input
2
4 2 0 1 0
50 50
4 1 0 2 1
100


Sample Output
8.14
2.00

*/
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
#include<math.h>
using namespace std;

#define eps 1e-9
const int MAXN=220;
double a[MAXN][MAXN],x[MAXN];//方程的左邊的矩陣和等式右邊的值,求解之后x存的就是結果
int equ,var;//方程數和未知數個數

int Gauss()
{
    int i,j,k,col,max_r;
    for(k=0,col=0;k<equ&&col<var;k++,col++)
    {
        max_r=k;
        for(i=k+1;i<equ;i++)
          if(fabs(a[i][col])>fabs(a[max_r][col]))
            max_r=i;
        if(fabs(a[max_r][col])<eps)return 0;
        if(k!=max_r)
        {
            for(j=col;j<var;j++)
              swap(a[k][j],a[max_r][j]);
            swap(x[k],x[max_r]);
        }
        x[k]/=a[k][col];
        for(j=col+1;j<var;j++)a[k][j]/=a[k][col];
        a[k][col]=1;
        for(i=0;i<equ;i++)
          if(i!=k)
          {
              x[i]-=x[k]*a[i][k];
              for(j=col+1;j<var;j++)a[i][j]-=a[k][j]*a[i][col];
              a[i][col]=0;
          }
    }
    return 1;
}

int num[MAXN];
double p[MAXN];
int cnt;
int n,N;//n=2*N-2
int M;
void bfs(int s)
{
    memset(num,-1,sizeof(num));
    queue<int>que;
    cnt=0;
    num[s]=cnt++;
    que.push(s);
    while(!que.empty())
    {
        int t=que.front();
        que.pop();
        for(int i=1;i<=M;i++)
        {
            if(fabs(p[i])<eps)continue;//這點很重要,這個想到不能達到的點
            int temp=(t+i)%n;
            if(num[temp]==-1)
            {
                num[temp]=cnt++;
                que.push(temp);
            }
        }
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int s,e;
    int D;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d%d",&N,&M,&e,&s,&D);
        for(int i=1;i<=M;i++){scanf("%lf",&p[i]);p[i]/=100;}

        if(e==s)//這個特判一定需要,否則可能N==1,會被0除,RE
        {
            printf("0.00\n");
            continue;
        }

        n=2*(N-1);
        if(D==1)s=n-s;
        bfs(s);
        if(num[e]==-1&&num[n-e]==-1)
        {
            printf("Impossible !\n");
            continue;
        }
        equ=var=cnt;
        memset(a,0,sizeof(a));
        memset(x,0,sizeof(x));
        for(int i=0;i<n;i++)
          if(num[i]!=-1)
          {
              if(i==e||i==n-e)
              {
                  a[num[i]][num[i]]=1;
                  x[num[i]]=0;
                  continue;
              }
              a[num[i]][num[i]]=1;
              for(int j=1;j<=M;j++)
              {
                  int t=(i+j)%n;
                  if(num[t]!=-1)
                  {
                      a[num[i]][num[t]]-=p[j];
                      x[num[i]]+=j*p[j];
                  }
              }
          }
        if(Gauss())printf("%.2lf\n",x[num[s]]);
        else printf("Impossible !\n");
    }
    return 0;
}

 

 

 今年概率DP就做到這吧!2012-10-6 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM