概率dp 入門

有dp就一定有狀態和狀態轉移,不同的就是概率dp處理的是概率或者期望

不知道該說點什麼,就貼貼代碼了。

A - Scout YYF I

經典的跳地雷的題目,有p的概率往前走一步,有1-p的概率跳兩格,求安全越過所有地雷的概率,這題由於數據量較大,推出公式後用矩陣快速冪做,poj的G++printf需要用%f才能過。。。

#include<cstdio>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn = 105;
const int maxe=300;
const int inf = 0x3f3f3f3f;
const int mod=1000000007;
int n,k,m;
double p;
double dp[maxn];
int place[maxn];
struct matrix{
    double ju[2][2];
    matrix(){memset(ju,0,sizeof(ju));}
    matrix operator *(matrix b){
        matrix cnt;
        for(int i=0;i<2;i++){
            for(int j=0;j<2;j++){
                for(int k=0;k<2;k++){
                    cnt.ju[i][j]+=ju[i][k]*b.ju[k][j];
                }
            }
        }
        return cnt;
    }
    void operator=(matrix b){
        for(int i=0;i<2;i++){
            for(int j=0;j<2;j++)ju[i][j]=b.ju[i][j];
        }
    }
};

double pow_mod(double a,double b,int c){
    matrix cc;
    cc.ju[0][0]=p,cc.ju[0][1]=1-p;
    cc.ju[1][0]=1;cc.ju[1][1]=0;
    matrix ans;
    ans.ju[0][0]=1;ans.ju[0][1]=0;
    ans.ju[1][0]=0;ans.ju[1][1]=1;
    int kkk=1;
    while(c){
        if(c%2)ans=ans*cc;
        c/=2;
        cc=cc*cc;
    }
    return a*ans.ju[0][0]+b*ans.ju[0][1];
}


int main(){
    while(cin>>n>>p){
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++){
           cin>>place[i];
        }
        sort(place+1,place+1+n);
        if(place[1]==0)dp[0]=0;
        else dp[0]=1;
        int cnt=1;
        for(int i=1;i<=n;i++){
            double kk;
            int step=place[i]-1-cnt;
            if(step<0){dp[i]=0;break;}
            if(step==0){kk=dp[i-1];}
            else if(step==1){kk=dp[i-1]*p;}
            else{
                kk=pow_mod(dp[i-1]*p,dp[i-1],step-1);
            }
            dp[i]=kk*(1-p);
            cnt=place[i]+1;
        }
            printf("%.7f\n",dp[n]);
    }

    return 0;
}

B - Collecting Bugs

找bug,有n種bug和m個子系統,每天可以找到一個bug,但是bug的種類和所處於子系統的概率都是均等的,求找到所有種類的bug以及在所有子系統種都找到bug的期望時間。
注意邊界處理

#include <string.h>
#include<cstdio>

using namespace std;
const int maxn=1005;
int n,m;
double dp[maxn][maxn];
bool vis[maxn][maxn];

double DP(int a,int b){
    if(vis[a][b])return dp[a][b];
    double ans=0;
    int k1=n-a,k2=m-b;
    if(k1&&k2)
    ans+=double(k1)/n*k2/m*(1+DP(a+1,b+1));
    if(k1&&b)
    ans+=double(k1)/n*double(b)/m*(1+DP(a+1,b));
    if(k2&&a)
    ans+=double(k2)/m*double(a)/n*(1+DP(a,b+1));
    if(a&&b){
        double p4=double(a)/n*b/m;
        ans+=p4;
        ans/=(1.0-p4);
    }
    vis[a][b]=1;
    return dp[a][b]=ans;
}

int main()
{
    while(~scanf("%d%d",&n,&m)){
        memset(dp,0,sizeof(dp));
        memset(vis,0,sizeof(vis));
        vis[n][m]=1;
        printf("%.4f\n",DP(0,0));
    }
    return 0;
}

C - One Person Game

搖骰子,若三個骰子的點數分別爲a,b,c,則分數歸零,否則分數+=a=b=c,分數大於等於n遊戲結束,求遊戲結束的期望時間

因爲每一個狀態都可能轉移到分數爲0的狀態,所以不能像之前那樣求了,得從公式中推出通式。

#include <string.h>
#include<cstdio>

using namespace std;
const int maxn=605;
const int maxm=35;
int n,k1,k2,k3,a,b,c;
double p0;
double p[6*6*6+5];
double A[maxn],B[maxn];


int main()
{
   int t;
   scanf("%d",&t);
   while(t--){
    memset(p,0,sizeof(p));
    scanf("%d%d%d%d%d%d%d",&n,&k1,&k2,&k3,&a,&b,&c);
    p0=1.0/k1/k2/k3;
    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;
}

D - Aeroplane chess

飛行棋,還有連接飛行線可以直接到另一個點,不過狀態轉移還是很簡單的。

#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=100005;
const int maxm=20005;
int n,m;
double dp[maxn];
bool vis[maxn];
bool line[maxn];
int to[maxn];

double DP(int i){
    if(i>=n)return 0;
    if(vis[i])return dp[i];
    if(line[i]){vis[i]=1;return dp[i]=DP(to[i]);}
    double ans=0;
    for(int j=1;j<=6;j++){
        ans+=1/6.0*(1+DP(i+j));
    }
    vis[i]=1;
    return dp[i]=ans;
}

int main(){
    int x,y;
    while(~scanf("%d%d",&n,&m),n+m){
        memset(line,0,sizeof(line));
        for(int i=0;i<m;i++){
            scanf("%d%d",&x,&y);
            line[x]=1;to[x]=y;
        }
        memset(vis,0,sizeof(vis));
        memset(dp,0,sizeof(dp));
        printf("%.4lf\n",DP(0));
    }

    return 0;
}

G - LOOPS

逃離迷宮的期望題,每一個grid都有一定概率傳送到右邊,上邊或者自身grid,問從左下角到右上角逃脫的期望。

#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=1005;
const int maxm=20005;
int r,c;
double p[maxn][maxn][3];
double dp[maxn][maxn];
bool vis[maxn][maxn];
double DP(int i,int j){
    if(i<0||j<0)return 0;
    if(vis[i][j])return dp[i][j];
    double ans=0;
    if(p[i][j][1])
    ans+=p[i][j][1]*(2+DP(i,j+1));
    if(p[i][j][2])
    ans+=p[i][j][2]*(2+DP(i+1,j));
    if(p[i][j][0]){
    ans+=2*p[i][j][0];
    ans/=(1-p[i][j][0]);
    }
    vis[i][j]=1;
    return dp[i][j]=ans;
}

int main(){
    while(~scanf("%d%d",&r,&c)){
        for(int i=0;i<r;i++){
            for(int j=0;j<c;j++){
                for(int k=0;k<3;k++)
                scanf("%lf",&p[i][j][k]);
            }
        }
        memset(vis,0,sizeof(vis));
        memset(dp,0,sizeof(dp));
        dp[r-1][c-1]=0;
        vis[r-1][c-1]=1;
        for(int i=0;i<r;i++){
            for(int j=0;j<c;j++){
                DP(i,j);
            }
        }
        printf("%.3lf\n",dp[0][0]);
    }
    return 0;
}

H - Check the difficulty of problems

給出每個隊伍ac每一題的概率,求冠軍隊伍(可以不止一支)出n題以上,其他隊伍都出1題以上的概率。
求出每個隊伍i出j題的概率,然後前綴和得到每個隊伍i做出0到j題的概率,然後就可以得到所有隊伍都a一道題以上的概率了,然後又可以求出所有隊伍都出n題以下的概率,減一下就得到答案了。

#include <string.h>
#include<cstdio>

using namespace std;
const int maxn=1000;
const int maxm=35;
int n,t,m;
double p[maxn][maxm];
double dp[maxn][maxm][maxm];
double s[maxn][maxm];
int main()
{
   while(scanf("%d%d%d",&m,&t,&n),m+t+n){
    memset(dp,0,sizeof(dp));
    memset(s,0,sizeof(s));
    for(int i=0;i<t;i++){
        for(int j=1;j<=m;j++){
            scanf("%lf",&p[i][j]);
        }
        dp[i][0][0]=1;
    }
    for(int i=0;i<t;i++){
        for(int j=1;j<=m;j++){
            for(int k=0;k<=j;k++){
                if(j)
                dp[i][j][k]=dp[i][j-1][k-1]*p[i][j]+dp[i][j-1][k]*(1-p[i][j]);
                else dp[i][j][k]=dp[i][j-1][k]*(1-p[i][j]);
            }
        }
    }
    for(int i=0;i<t;i++){
        s[i][0]=dp[i][m][0];
        for(int j=1;j<=n;j++){
            s[i][j]=s[i][j-1]+dp[i][m][j];
        }
    }
    double pp=1;
    for(int i=0;i<t;i++){
        pp*=1-s[i][0];
    }
    double pp2=1;
    for(int i=0;i<t;i++){
        pp2*=s[i][n-1]-s[i][0];
    }
    printf("%.3lf\n",pp-pp2);
   }
    return 0;
}

I - Bag of mice

兩個人輪流抓老鼠,知道怎麼轉移狀態就行了。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<cmath>
#include<stdio.h>
using namespace std;
int w,b;
double v[1005][1005];
double dfs(int white,int black){
    if(white==0)return 0.0;//only black
    else if(black==0){return 1.0;}//only white
    if(v[white][black]!=0)return v[white][black];
    double win=0;
    double cw=double(white)/double(white+black);
    win+=double(white)/double(white+black);//gongzhuchoubai
    cw=(1.0-cw)*double(black-1)/double(white+black-1);//mowang chou hei
    double cnt=0.0;
    if(black>=3){
        cnt+=dfs(white,black-3)*(double(black-2)/double(black+white-2));
    }
    if(white>=1&&black>=2){
        cnt+=dfs(white-1,black-2)*(double(white)/double(black+white-2));
    }
    win+=cw*cnt;
    v[white][black]=win;
    return win;
}


int main(){
    scanf("%d%d",&w,&b);
    printf("%.9lf",dfs(w,b));
    return 0;
}

J - Football

足球比賽,求某隊成爲冠軍的概率最大,需要注意的就是某個隊伍i在第j輪可以遇到的對手是那些隊伍(因爲賽制原因),dp[i][j]表示隊伍i在第j輪獲勝的概率。

#include<cstdio>
#include<string.h>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=105;
const int maxm=20005;
int n,m;
int total;
double p[200][200];
double dp[200][10];
bool vis[200][200];
double DP(int i,int j){
    if(vis[i][j])return dp[i][j];
    double ans=0;
    int cnt=pow(2,j);
    int last=pow(2,j-1);
    int k1=i/last;
    int kk=i/cnt;
    for(int z=kk*cnt;z<(kk+1)*cnt;z++){
        if(z>=k1*last&&z<(k1+1)*last)continue;
        ans+=DP(z,j-1)*p[i][z];
    }
    ans*=dp[i][j-1];
    vis[i][j]=1;
    return dp[i][j]=ans;
}

int main(){
    while(scanf("%d",&n),n!=-1){
        memset(dp,0,sizeof(dp));
        memset(vis,0,sizeof(vis));
        total=pow(2,n);
        for(int i=0;i<total;i++){
            dp[i][0]=1;
            vis[i][0]=1;
            for(int j=0;j<total;j++){
                scanf("%lf",&p[i][j]);
            }
        }
        for(int j=1;j<=n;j++){
            for(int i=0;i<total;i++){
                DP(i,j);
            }
        }
        int winner=-1;
        double g=0;
        for(int i=0;i<total;i++){
            if(dp[i][n]>g){
                g=dp[i][n];
                winner=i+1;
            }
        }
        printf("%d\n",winner);
    }
    return 0;
}

K - Kids and Prizes

從禮物的角度來看,只有拿或者被拿兩種狀態,所有孩子選房間的概率都是相等不變的,所以每一個禮物被拿走的概率都相等,其實就變成了n重伯努利實驗了,然後期望就是np

M - Help Me Escape

簡單期望題。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
const int maxm=20005;
int n,l;
double p;
int t[maxn];
int c[maxn];
double dp[maxm];

double cal(int cnt){
    if(dp[cnt])return dp[cnt];
    double ans=0;
    for(int i=0;i<n;i++){
        if(cnt>c[i]){
            ans+=t[i]*p;
        }
        else{
            ans+=(1+cal(cnt+c[i]))*p;
        }
    }
    return dp[cnt]=ans;
}


int main(){
    while(~scanf("%d%d",&n,&l)){
        for(int i=0;i<n;i++){
            scanf("%d",&c[i]);
            t[i]=(sqrt(double(5))+1)/2.0*c[i]*c[i];
        }
        p=1.0/n;
        memset(dp,0,sizeof(dp));
        printf("%.3lf\n",cal(l));
    }
    return 0;
}
發佈了113 篇原創文章 · 獲贊 41 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章