概率dp入门篇

概率dp入门篇

 

1.hdu 3853 LOOPS

  思路:用dp[i][j]表示在i,j点的期望步数,p[i][j][k](k=0-2)表示i,j点的3个概率,假设所有期望都是已知:

    则dp[i][j]=p[i][j][0]*(dp[i][j]+2)+p[i][j][1]*(dp[i][j+1]+2)+p[i][j][2]*(dp[i+1][j]+2);

  由于p[i][j][0]+p[i][j][1]+p[i][j][2]=1为了方便表示,可以把2提出来写成:

    dp[i][j]=p[i][j][0]*dp[i][j]+p[i][j][1]*dp[i][j+1]+p[i][j][2]*dp[i+1][j]+2;

  现在等号左右两边都有dp[i][j],移项得:

    dp[i][j]=(p[i][j][1]*dp[i][j+1]+p[i][j][2]*dp[i+1][j]+2)/(1-p[i][j][0]);

  现在解法很明显了,要求dp[i][j],先知道dp[i+1][j]和dp[i][j+1]即可,由于dp[r-1][c-1]是已知的0,所以倒推即可得到所有的dp[i][j].

HDU 3853 
#include<stdio.h>
double p[1010][1010][3];
double dp[1010][1010];
int main()
{
    int r,c,i,j,k;
    while(scanf("%d%d",&r,&c)!=EOF){
        for(i=0;i<r;i++)
            for(j=0;j<c;j++)
                for(k=0;k<3;k++)
                    scanf("%lf",&p[i][j][k]);
        dp[r-1][c-1]=0;
        p[r-1][c-1][0]=1;
        for(i=r-1;i>=0;i--){
            for(j=c-1;j>=0;j--){
                if(p[i][j][0]==1) continue;
                double factor=1/(1-p[i][j][0]);
                dp[i][j]=(p[i][j][1]*dp[i][j+1]+p[i][j][2]*dp[i+1][j]+2)*factor;
            }
        }
        printf("%.3lf\n",dp[0][0]);
    }
    return 0;
}

 

 2.hdu 4405 Aeroplane chess

  思路:同样是倒推,如果当前点可以传送,则该点的期望直接等于它传送到的那个点,否则可列出下式:

  dp[i]=∑(dp[i+k]+1)*p[k]     (k取1-6,p[k]=1/6)

HDU 4405
#include<stdio.h>
#include<string.h>
double dp[100020];
int next[100020];
int main()
{
    int n,m,i,x,y,k;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0) break;
        for(i=0;i<=n+6;i++)
            dp[i]=next[i]=0;
        while(m--){
            scanf("%d%d",&x,&y);
            next[x]=y;
        }
        double temp=1.0/6;
        for(i=n-1;i>=0;i--){
            if(next[i]){
                dp[i]=dp[next[i]];
                continue;
            }
            for(k=1;k<=6;k++){
                dp[i]+=(dp[i+k]+1)*temp;
            }
        }
        printf("%.4lf\n",dp[0]);
    }
    return 0;
}

 

3.zoj 3329 One Person Game

  该题的想法学习自该博客:http://blog.csdn.net/morgan_xww/article/details/6775853

  思路:上一题的升级版,每次可以扔三个骰子,且数字个数不定,可列出式子:

  dp[i]=dp[0]*p0+∑dp[i+k]*P[k]  (P0即回到0点的概率,P[k]是点数总和是k的概率)

  而我们想求的也是dp[0],明显当前的递推式是有环的,因为P[0]在每一项都出现,我们假设P[0]可以表示任一项,即设:

  dp[i]=fa[i]*dp[0]+fb[i];

  带入1式可得:

  dp[i]=(∑P[k]*fa[i+k]+p0)*dp[0]+∑P[k]*fb[i+k]+1;

  和1式的每一项对应可得:

  fa[i]=∑P[K]*fa[i+k]+p0;

  fb[i]=∑P[K]*fb[i+k]+1;

  倒推出所有的a和b,最后答案就是dp[0]=fb[0]/(1-fa[0])

  

ZOJ 3329
#include<stdio.h>
#include<string.h>
double fa[550],fb[550];
double p[40];
int main(){
    int T,i,j,k,n,k1,k2,k3,a,b,c;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
        memset(fa,0,sizeof(fa));
        memset(fb,0,sizeof(fb));
        memset(p,0,sizeof(p));
        int max=k1+k2+k3;
        double temp=1.0/(k1*k2*k3);
        for(i=1;i<=k1;i++)
            for(j=1;j<=k2;j++)
                for(k=1;k<=k3;k++)
                    if(i!=a||j!=b||k!=c)
                        p[i+j+k]+=temp;
        for(i=n;i>=0;i--){
            for(j=3;j<=max;j++){
                fa[i]+=fa[i+j]*p[j];
                fb[i]+=fb[i+j]*p[j];
            }
            fa[i]+=temp;
            fb[i]+=1;
        }
        printf("%.15lf\n",fb[0]/(1-fa[0]));
    }
    return 0;
}

 

4.poj 2096 Collecting Bugs

  思路:用dp[i][j]表示当前已经找到了i个子系统j个bug,可得到:

    dp[i][j]=((dp[i][j]*i*j+dp[i+1][j]*(n-i)*j+dp[i][j+1]*i*(s-j)+dp[i+1][j+1])*(n-i)*(s-j)+1 )/n*s;

  化简同第一题.

POJ 2096 
#include<stdio.h>
#include<string.h>
double dp[1010][1010];
int main()
{
    int n,s,i,j;
    while(scanf("%d%d",&n,&s)!=EOF){
        memset(dp,0,sizeof(dp));
        for(i=n;i>=0;i--){
            for(j=s;j>=0;j--){
                if(i==n&&j==s) continue;
                double factor=n*s-i*j;
                dp[i][j]=dp[i+1][j]*j*(n-i)+dp[i][j+1]*(s-j)*i+dp[i+1][j+1]*(n-i)*(s -j)+n*s;
                dp[i][j]/=factor;
            }
        }
        printf("%.4lf\n",dp[0][0]);
    }
    return 0;
}

 

5.hdu 4050 wolf5x

  思路:这题可以有两种思考的方向,即正推和反推,不过明显反推的方法实现简单点而且效率要高.

  反推法:

HDU 4050
#include<stdio.h>
#include<string.h>
double p[4100][4],dp[4100][4];
int main()
{
    int T,i,j,k,n,a,b;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&a,&b);
        memset(p,0,sizeof(p));
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++)
            for(j=0;j<4;j++)
                scanf("%lf",&p[i][j]);
        for(i=n+1;i<=4000;i++)
            p[i][3]=1;
        p[0][3]=dp[0][3]=1;
        for(i=0;i<=n;i++)
        {
            for(j=1;j<4;j++)
            {
                double NOT=1;
                for(k=a;k<=b;k++)
                {
                    if(j==1){
                        dp[i+k][2]+=dp[i][j]*p[i+k][2]*NOT;
                        dp[i+k][3]+=dp[i][j]*p[i+k][3]*NOT;
                        NOT*=(p[i+k][0]+p[i+k][1]);
                    }
                    if(j==2){
                        dp[i+k][1]+=dp[i][j]*p[i+k][1]*NOT;
                        dp[i+k][3]+=dp[i][j]*p[i+k][3]*NOT;
                        NOT*=(p[i+k][0]+p[i+k][2]);
                    }
                    if(j==3){
                        dp[i+k][1]+=dp[i][j]*p[i+k][1]*NOT;
                        dp[i+k][2]+=dp[i][j]*p[i+k][2]*NOT;
                        dp[i+k][3]+=dp[i][j]*p[i+k][3]*NOT;
                        NOT*=(p[i+k][0]);
                    }
                    if(k+i>n) break;
                }
            }
        }
        double ans=0;
        for(i=1;i<=n+a;i++)
            for(j=0;j<4;j++)
                ans+=dp[i][j];
        printf("%.8lf\n",ans);
    }
    return 0;
}

 

  正推法:

HDU 4050
#include<stdio.h>
#include<string.h>
double p[2010][4],dp[2010][4];
int main(){
    int T,n,a,b,i,j,k;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&a,&b);
        for(i=1;i<=n;i++)
            scanf("%lf%lf%lf%lf",&p[i][0],&p[i][1],&p[i][2],&p[i][3]);
        memset(dp,0,sizeof(dp));
        p[0][3]=1;
        p[0][1]=p[0][2]=p[0][0]=0;
        for(i=n;i>=0;i--){
            for(j=1;j<=4;j++){
                double NOT=1,temp;
                for(k=a;k<=b;k++){
                    if(k+i>n){
                        dp[i][j]+=NOT;
                        break;
                    }
                    if(j==1){
                        temp=(dp[i+k][2]+1)*p[i+k][2]+(dp[i+k][3]+1)*p[i+k][3];
                        dp[i][j]+=temp*NOT;
                        NOT*=(p[i+k][0]+p[i+k][1]);
                    }
                    if(j==2){
                        temp=(dp[i+k][1]+1)*p[i+k][1]+(dp[i+k][3]+1)*p[i+k][3];
                        dp[i][j]+=temp*NOT;
                        NOT*=(p[i+k][0]+p[i+k][2]);
                    }
                    if(j==3){
                        temp=(dp[i+k][1]+1)*p[i+k][1]+(dp[i+k][2]+1)*p[i+k][2]+(dp[i+k][3]+1)*p[i+k][3];
                        dp[i][j]+=temp*NOT;
                        NOT*=(p[i+k][0]);
                    }
                }
            }
        }
        printf("%.8lf\n",dp[0][3]);
    }
    return 0;
}


6.hdu 4336 Card Collector

  思路:状态+递推,容易列出式子:dp[i]=dp[i]*P(空袋子+已有卡片)+dp[j]*P[j]+1;

  j表示i状态中不包含的卡片,dp[j]表示i增加第j张卡片后的状态,P[j]表示第j张卡片出现的概率,移项后递推就行了.

  注意:样例有点坑..如果只保留3位无法满足精度要求.

HDU 4336
#include<stdio.h>
double p[25],dp[2000000];

int main(){
    int n,i,j;
    while(scanf("%d",&n)!=EOF){
        double empty=1;
        for(i=0;i<n;i++){
            scanf("%lf",&p[i]);
            empty-=p[i];
        }
        int maxstate=(1<<n)-1;
        dp[maxstate]=0;
        for(i=maxstate-1;i>=0;i--){
            double tp=empty,temp=0;
            for(j=0;j<n;j++){
                if(i&(1<<j))
                    tp+=p[j];
                else
                    temp+=(dp[i+(1<<j)])*p[j];
            }
            dp[i]=(temp+1)/(1-tp);
        }
        printf("%.8lf\n",dp[0]);
    }
    return 0;
}

 

 

 

 

posted @ 2013-04-24 18:45  破晓べ  阅读(694)  评论(0编辑  收藏  举报