概率dp入门篇
概率dp入门篇
思路:用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].
#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; }
思路:同样是倒推,如果当前点可以传送,则该点的期望直接等于它传送到的那个点,否则可列出下式:
dp[i]=∑(dp[i+k]+1)*p[k] (k取1-6,p[k]=1/6)
#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; }
该题的想法学习自该博客: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])
#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; }
思路:用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;
化简同第一题.
#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; }
思路:这题可以有两种思考的方向,即正推和反推,不过明显反推的方法实现简单点而且效率要高.
反推法:
#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; }
正推法:
#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; }
思路:状态+递推,容易列出式子:dp[i]=dp[i]*P(空袋子+已有卡片)+dp[j]*P[j]+1;
j表示i状态中不包含的卡片,dp[j]表示i增加第j张卡片后的状态,P[j]表示第j张卡片出现的概率,移项后递推就行了.
注意:样例有点坑..如果只保留3位无法满足精度要求.
#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; }