有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;
}