概率 DP
一直在等学习概率论这门课后再开,但是老师一节课讲的内容我自己也能看完,恰巧昨天打了一次比赛遇到求期望DP,是时候学一下了。
概率DP主要用于求解期望、概率等题目。
转移方程有时比较灵活。
一般求概率是正推,求期望是逆推。通过题目可以体会到这点。 ——by kuangbin
首先先推荐几篇参考的论文
不难发现,我们可以单独求每一个人的期望。
如果把单独求概率或者期望是很难想的。
考虑将两者组合起来求
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] 之和
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
这道题其实比较明显,期望等于概率
我们不妨设 f[i][j] 为 i 只老虎,j 只鹿的生存概率。
很容易求出状态转移方程。
我自认为我的代码还是比较优美易懂的,这里不展开写了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
你处于一个神秘空间中,在你面前有 n 扇门,你穿越这些门需要花费一定的时间,也就是门上标记数字的绝对值,如果是正数,穿越门后你会走出迷宫;否则,你会留在原点。
你可以记住最后选择的 k 扇门,保证下次选择时不会选择这 k 扇门,求走出迷宫的期望。
先求出正门的平均值,再求出负门的平均值,然后计算对应的概率和时间的乘积即可。
最后要用到等比数列求和公式以及错位相减,属于不简单但是比较常规的题目。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
逆向求期望
要求期望次数,要么是 连续失败 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)
两个未知数,两个方程,可解
我也是看别人的题解才勉强会的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
积的期望=期望的积
题意:给你一个数字 n 和变化次数 k,我们定义一次变换为将 x 变为它的一个因数(包括1和x),变成任意一个因数的概率是相同的。
求经过 k 次变换后最终数字的期望,答案对 1e9+7 取模。
1<=n<=1e15,1<=k<=1e4
思路:首先可以 sqrt(n) 的求出 n 的所有因数,对于 1e15 的级别来说,有多达 250多个因数,彼此建边进行传递的话,需要 250*250*k 的时间复杂度,很明显超时。
这里引入一下欧拉函数:
我们都知道,任意一个数字都可以被欧拉函数表示,而欧拉函数是积性函数,那么我们可以通过欧拉函数将数字分解,单独求各部分的贡献,最后将各部分的贡献相乘即可。
对于构成 n 的各个质数,传递过程中彼此间是独立的、互不影响的,所以我们可以利用这个性质单独求每一种质数的概率,最后相乘即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
几何分布的概率问题
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)
也就是调和级数。
面对这样一个黑盒子,每个棒子被抽到的概率相同,那么期望次数也相同,不同的在于一些棒子质量只会被记录一次,把这些棒子的质量和另一种棒子的质量本别计算即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
前后依赖的概率问题
题意是给你两个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
10*10 的网格,求从 1 走到 100 的期望步数。通过摇骰子决定步数,每步可以走 1~6 格,其中一些格子是传送点,可以传送到它前面或者后面的另一个格子。如果走出了网格,重来。
对于非传送点:f[x]=1/6(f[x-1]+...+f[x-6]+6)
对于传送点:f[x]=f[go[x]]
然后根据上式采用高斯消元解出一百元一次方程即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
DP很暴力,也很美~