概率 DP

一直在等学习概率论这门课后再开,但是老师一节课讲的内容我自己也能看完,恰巧昨天打了一次比赛遇到求期望DP,是时候学一下了。

概率DP主要用于求解期望、概率等题目。

转移方程有时比较灵活。

一般求概率是正推,求期望是逆推。通过题目可以体会到这点。  ——by kuangbin


 

首先先推荐几篇参考的论文

《信息学竞赛中概率问题求解初探》

《浅析竞赛中一类数学期望问题的解决方法》

《有关概率和期望问题的研究 》

 

 

Kevin的抽奖黑幕

不难发现,我们可以单独求每一个人的期望。

如果把单独求概率或者期望是很难想的。

考虑将两者组合起来求

dp[i][j]到 i 表示连续 j 次没有拿到礼物的概率

所以:

  dp[i][j]=dp[i-1][j-1]*c           (j>0)

  dp[i][j]=(dp[i-1][0]+...+dp[i-1][d])*(1-c)    (j=0)

我们所求的期望就是 dp[i][d] 之和

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
#define int long long
#define pb push_back
using namespace std;
int n,m,t,k,qq,d;
const int N=2e3+10;
const int mod=998244353;
int a[N],h[N],b[N];
int qmi(int x,int k){
    int res=1;
    while(k){
        if(k&1) res=res*x%mod;
        x=x*x%mod,k>>=1;
    }
    return res;
}
int inv(int x){
    return qmi(x,mod-2);
}
int dp[N],f[N];
void init(){

}
void solve(){
    cin>>n>>m>>k>>d;
    int c=inv(n)*k%mod,ans=0;
    for(int i=0;i<=d;++i) f[i]=0;
    f[0]=n;
    for(int i=1;i<=m;++i){
        for(int j=0;j<d;++j){
            dp[0]=(dp[0]+f[j]*c%mod)%mod;
            dp[j+1]=f[j]*(mod+1-c)%mod;
        }
        ans+=dp[d];
        dp[0]+=dp[d];dp[0]%=mod;
        dp[d]=0;
        for(int i=0;i<=d;++i) f[i]=dp[i],dp[i]=0;
    }
    ans=(ans+m*k)%mod;
    cout<<ans<<endl;
}
signed main(){
    int T=1;
    cin>>T;
    init();
    while(T--) solve();
    return 0;
}
View Code

 

Island of Survival

这道题其实比较明显,期望等于概率

我们不妨设 f[i][j] 为 i 只老虎,j 只鹿的生存概率。

很容易求出状态转移方程。

我自认为我的代码还是比较优美易懂的,这里不展开写了。

#include<bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N=1e4+10;
int n,m,t,k,qq,cnt,d;
double a[N],f[N][N];
int qmi(int a,int k){
    int res=1;
    while(k){
        if(k&1) res=res*a%mod;
        a=a*a%mod;k>>=1;
    }
    return res;
}
void init(){
    for(int i=0;i<=1e3;i+=2){
        for(int j=0;j<=1e3;++j){
            if(!i) {f[i][j]=1.00000;continue;}
            if(!j){
                f[i][j]=f[i-2][j]*(i-1)/(i+1);
            }
            else if(j==1){
                double u=i*(i-1)/2+i+j;
                double x=(i-1)*i/2/u*f[i-2][j]+j/u*f[i][j-1];
                double y=(i-1)*i/2/u*f[i-2][j]/(1-j/u);
                f[i][j]=max(x,y);
            }
            else{
                double u=i*(i-1)/2+i+j+j*(j-1)/2;
                double x=i*(i-1)/2/u*f[i-2][j]/(1-j/u-j*(j-1)/2/u);
                double y=(i*(i-1)/2/u*f[i-2][j]+j/u*f[i][j-1])/(1-j*(j-1)/2/u);
                f[i][j]=max(x,y);
            }
        }
    }


}
void solve(){
    ++cnt;
    cin>>n>>m;
    printf("Case %d: %.6f\n",cnt,f[n][m]);
}
signed main(){
    int T=1;
    cin>>T;
    init();
    while(T--) solve();
    return 0;
}
View Code

 

A Dangerous Maze (II)

你处于一个神秘空间中,在你面前有 n 扇门,你穿越这些门需要花费一定的时间,也就是门上标记数字的绝对值,如果是正数,穿越门后你会走出迷宫;否则,你会留在原点。

你可以记住最后选择的 k 扇门,保证下次选择时不会选择这 k 扇门,求走出迷宫的期望。

先求出正门的平均值,再求出负门的平均值,然后计算对应的概率和时间的乘积即可。

最后要用到等比数列求和公式以及错位相减,属于不简单但是比较常规的题目。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,t,k,qq,tot;
int a[N],f[N];
void init(){

}
void solve(){
    ++tot;
    cin>>n>>k;
    int cnt1=0,cnt2=0;
    double sum1=0,sum2=0;
    for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=1;i<=n;++i){
        if(a[i]>0) sum1+=a[i],++cnt1;
        else sum2-=a[i],++cnt2;
    }
    double u1=sum1*1.00/cnt1;
    double u2=sum2*1.00/cnt2;
    double ans=0.00;
    if(cnt2==n){  printf("Case %d: -1\n",tot);return; }
    if(!cnt2){ printf("Case %d: %.6f\n",tot,u1);return; }
    if(k>=cnt2){
        k=cnt2;
        double now=1.00;
        for(int i=1;i<=k;++i){
            ans+=now*cnt1/(n-i+1)*(u1+u2*(i-1));
            now*=1.00*(cnt2-i+1)/(n-i+1);
        }
        ans+=now*(u1+k*u2);
    }
    else{
        double now=1.00;
        for(int i=1;i<=k;++i){
            ans+=now*cnt1/(n-i+1)*(u1+u2*(i-1));
            now*=1.00*(cnt2-i+1)/(n-i+1);
        }
        double p=1.00*cnt1/(n-k);
        double q=1-p;
        ans+=u1*now+u2*now*k;
        ans+=u2*q/(1-q)*now;
    }
     printf("Case %d: %.6f\n",tot,ans);
}
signed main(){
    int T=1;
    cin>>T;
    init();
    while(T--) solve();
    return 0;
}
View Code

 

 

逆向求期望

 Batting Practice

要求期望次数,要么是 连续失败 k2 次,要么是 连续成功 k1 次。

 p:成功的概率    q=1-p

定义 f[x]:已经连续成功 x 次距离结束的期望次数

定义 g[x]:已经连续失败 x 次距离结束的期望次数

转移方程:

  f[x]=f[x+1]*p+g[1]*q+1

  g[x]=g[x+1]*q+f[1]*p+1

最终答案就是 p*f[1]+q*g[1]+1

f[k1]=0,g[k2]=0

运用高中的等比数列求和公式推一推,就可以求出表达式

f[1]=(q*g[1]+1)*(1-pow(p,k1-1))/(1-p)

g[1]=(p*f[1]+1)*(1-pow(q,k2-1))/(1-q)

两个未知数,两个方程,可解

我也是看别人的题解才勉强会的

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e2+10;
int n,m,t,k,qq,cnt;
int x,y,z,kk;
double f[N];//连续成功 x 次的期望,距离结束训练所需期望
double g[N];//连续失败 x 次的期望,距离结束训练所需期望
void solve(){
    ++cnt;
    double p,q;
    int k1,k2;
    cin>>q>>k1>>k2;
    p=1-q;
    if(q==0.000){
        printf("Case %d: %d\n",cnt,k1);return;
    }
    if(q==1.000){
        printf("Case %d: %d\n",cnt,k2);return;
    }
    double f=((1-pow(q,k2-1))*(1-pow(p,k1-1))/p+(1-pow(p,k1-1))/q)/(pow(p,k1-1)+pow(q,k2-1)-pow(p,k1-1)*pow(q,k2-1));
    double g=(p*f+1)*(1-pow(q,k2-1))/p;
    double ans=p*f+q*g+1.00;
    printf("Case %d: %.6f\n",cnt,ans);
}
int main(){
    cin>>t;
    while(t--) solve();
    return 0;
}
View Code

 

 积的期望=期望的积

题意:给你一个数字 n 和变化次数 k,我们定义一次变换为将 x 变为它的一个因数(包括1和x),变成任意一个因数的概率是相同的。

   求经过 k 次变换后最终数字的期望,答案对 1e9+7 取模。

     1<=n<=1e15,1<=k<=1e4

思路:首先可以 sqrt(n) 的求出 n 的所有因数,对于 1e15 的级别来说,有多达 250多个因数,彼此建边进行传递的话,需要 250*250*k 的时间复杂度,很明显超时。

   这里引入一下欧拉函数:

     我们都知道,任意一个数字都可以被欧拉函数表示,而欧拉函数是积性函数,那么我们可以通过欧拉函数将数字分解,单独求各部分的贡献,最后将各部分的贡献相乘即可。

     对于构成 n 的各个质数,传递过程中彼此间是独立的、互不影响的,所以我们可以利用这个性质单独求每一种质数的概率,最后相乘即可。

 

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+10;
const int mod=1e9+7;
int n,m,t,k,qq,cnt;
int a[N],f[N],dp[N][N],DP[N][N],b[N],d[N];
int qmi(int x,int k){
    int res=1;
    while(k){
        if(k&1) res=res*x%mod;
        x=x*x%mod,k>>=1;
    }
    return res;
}
int qni(int x){
    return qmi(x,mod-2);
}
void init(){

}
void solve(){
    cin>>n>>k;
    for(int i=2;i*i<=n;++i){
        if(n%i==0){
            a[++cnt]=i;
            while(n%i==0) n/=i,b[cnt]++;
        }
    }
    if(n>1) a[++cnt]=n,b[cnt]=1;
    for(int i=1;i<=cnt;++i) dp[i][b[i]]=1;
    for(int i=1;i<=k;++i){
        for(int j=1;j<=cnt;++j){
            for(int k=b[j];k>=0;--k){
                for(int l=k;l>=0;--l){
                    DP[j][l]=(DP[j][l]+dp[j][k]*qni(k+1))%mod;
                }
            }
        }
        for(int j=1;j<=cnt;++j){
            for(int k=0;k<=b[j];++k)
                dp[j][k]=DP[j][k],DP[j][k]=0;
        }
    }

    int ans=1;
    for(int i=1;i<=cnt;++i){
        int now=0;
        for(int j=0;j<=b[i];++j) now=(now+dp[i][j]*qmi(a[i],j)%mod)%mod;
        ans=ans*now%mod;
    }
    cout<<ans;
}
signed main(){
    int T=1;
    //cin>>T;
    init();
    while(T--) solve();
    return 0;
}
View Code

 

 

 几何分布的概率问题

 Aladdin and the Magical Sticks

在一个黑盒子里有无穷多的棒子,其中一些你可以分辨,称为 A 棒,另一种你不能分辨,称为 B 棒。

一共有 n 种棒子,每种棒子都有对应的质量 a[i]。现在,你想把每种品类的棒子都收集至少一根,在拿到一根棒子后,如果你确定你已经拥有该棒,则此棒可以不放入你的袋子里。问收集 n 种棒子的期望质量。

 

首先考虑一个子问题:对于一个六面筛子,每次记录最上方的面,你需要摇多少次才能把六个面都记录到?

获得第一个新面的概率是 1;获得第二个新面的概率是 5/6...获得最后一个新面的概率是 1/6

所以期望为 6/6+6/5+6/4+6/3+6/2+6/1=6*(1/6+1/5+1/4+1/3+1/2+1/1)

也就是调和级数。

 

面对这样一个黑盒子,每个棒子被抽到的概率相同,那么期望次数也相同,不同的在于一些棒子质量只会被记录一次,把这些棒子的质量和另一种棒子的质量本别计算即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,t,k,qq,tot;
int a[N],f[N],b[N];
void init(){

}
void solve(){
    cin>>n;
    int cnt=0;double ans=0,sum=0;
    for(int i=1;i<=n;++i) cin>>a[i]>>b[i];
    for(int i=1;i<=n;++i){
        if(b[i]==1) ans+=a[i];
        else ++cnt,sum+=a[i];
    }
    double uu=0;
    for(int i=1;i<=n;++i) uu+=n*1.00/(i);
    uu/=n;
    ans+=sum*uu;
    printf("Case %d: %.7f\n",++tot,ans);
}
signed main(){
    int T=1;
    cin>>T;
    init();
    while(T--) solve();
    return 0;
}
View Code

 

 前后依赖的概率问题

D. Flexible String Revisit

题意是给你两个01字符串,随机翻转第一个字符串(0-1,1-0),两个字符串第一次相等的次数的期望。

设 f[i] 为已经匹配了 i 个字符到达终态还需翻转的次数,那么:

  

 ...

 

f[i] 同时依赖于 f[i+1] 和 f[i-1],仅有 f[0] 只依赖 f[1]

那么我们设 f[i] = f[i+1] + x[i]

f[i]= f[i+1]*(i/n) + f[i-1]*(1-i/n)

f[i]= f[i+1]*(i/n) + (f[i] + x[i-1])*(1-i/n)

f[i]= f[i+1]+ x[i-1]*(n-i)/i

既 x[i]= x[i-1]*(n-i)/i

而 x[0]= 1

 

想半天没想出来,更多还是技巧性,可能高中时候还能搞一搞

#include<bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N=1e6+10;
int n,m,t,k,qq;
int a[N],f[N],x[N];
int qmi(int x,int k){
    int res=1;
    while(k){
        if(k&1) res=res*x%mod;
        x=x*x%mod,k>>=1;
    }
    return res;
}
void init(){

}
void solve(){
    cin>>n;
    int cnt=0;
    string s1,s2;
    cin>>s1>>s2;
    for(int i=1;i<=n;++i) if(s1[i-1]!=s2[i-1]) ++cnt;
    x[0]=1;f[n]=0;
    for(int i=1;i<n;++i) x[i]=(i*x[i-1]%mod*qmi(n-i,mod-2)%mod+n*qmi(n-i,mod-2)%mod)%mod;
    for(int i=n-1;i>=0;--i) f[i]=(f[i+1]+x[i])%mod;
    cout<<f[n-cnt]<<endl;
}
signed main(){
    int T=1;
    cin>>T;
    init();
    while(T--) solve();
    return 0;
}

 

 

用高斯消元求解的概率DP

 Snakes and Ladders

10*10 的网格,求从 1 走到 100 的期望步数。通过摇骰子决定步数,每步可以走 1~6 格,其中一些格子是传送点,可以传送到它前面或者后面的另一个格子。如果走出了网格,重来。

对于非传送点:f[x]=1/6(f[x-1]+...+f[x-6]+6)

对于传送点:f[x]=f[go[x]]

然后根据上式采用高斯消元解出一百元一次方程即可

#include<bits/stdc++.h>
#define int long long
#define mod 998244353
#define eps 1e-8
using namespace std;
const int N=1e2+10;
int n,m,t,k,qq,cnt,d;
int a[N];
double dp[N][N];
int qmi(int a,int k){
    int res=1;
    while(k){
        if(k&1) res=res*a%mod;
        a=a*a%mod;k>>=1;
    }
    return res;
}
void init(){

}
void guass(int n,int m){
    int col,row,mxr;
    for(col=row=1;row<=n&&col<=m;++row,++col){
        mxr=row;
        for(int i=row+1;i<=n;++i) if(fabs(dp[i][col])>fabs(dp[mxr][col])) mxr=i;
        if(mxr!=row) swap(a[row],a[mxr]);
        if(fabs(dp[row][col]<eps)){ --row;continue; }
        for(int i=1;i<=n;++i){
            if(i!=row&&fabs(dp[i][col])>eps){
                for(int j=m;j>=col;--j) dp[i][j]-=dp[row][j]/dp[row][col]*dp[i][col];
            }
        }
    }
    row--;
    for(int i=row;i;--i){
        for(int j=i+1;j<=row;++j) dp[i][m]-=dp[j][m]*dp[i][j];
        dp[i][m]/=dp[i][i];
    }
}
void solve(){
    memset(a,0,sizeof a);
    memset(dp,0,sizeof dp);
    cin>>n;
    for(int i=1;i<=n;++i){
        int u,v;
        cin>>u>>v;
        a[u]=v;
    }
    for(int i=1;i<100;++i){
        if(a[i]){
            dp[i][a[i]]=-1;
            dp[i][101]=0;
            dp[i][i]=1;
        }
        else{
            int cnt=0;
            for(int j=1;j+i<=100&&j<=6;++j){
                ++cnt;
                dp[i][i+j]=-1;
            }
            dp[i][i]=cnt;
            dp[i][101]=6;
        }
    }
    dp[100][100]=1;
    dp[100][101]=0;
    guass(100,101);
    printf("Case %d: %.6f\n",cnt,dp[1][101]);
}
signed main(){
    int T=1;
    cin>>T;
    init();
    while(T--) solve();
    return 0;
}
View Code

 

 

 

DP很暴力,也很美~

posted @ 2023-08-26 09:31  青阳buleeyes  阅读(7)  评论(0编辑  收藏  举报