最近学习了一下概率dp,感觉没有想象中的那么难。主要还是状态的转移方程的构建。
对于”求概率正推,求期望反推“这句话有了初步的理解。期望的意思是说你现在处在一个状态,还需要几步到达最终状态,这是一个期望值,
假如我现在已经在目标状态,那么期望是0,即解决了初始化的问题,否则并不知道起点的期望值。
附上几道入门题:
hdu 4405 http://acm.hdu.edu.cn/showproblem.php?pid=4405
题意:飞行棋,每次掷骰子,向前移动的距离是当前位置+骰子的点数(1~6),还有一些直达点,求由0到n的掷骰子次数的期望。
逆推。从终点逆推回来,状态的转移看文中代码把。
//hdu 4405
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<iomanip>
using namespace std;
int _hash[100010];
double dp[100010];
int main()
{
int n,m,x,y;
while(cin>>n>>m)
{
if(n==0&&m==0)
break;
memset(_hash,-1,sizeof(_hash));
for(int i=1;i<=m;i++)
{
cin>>x>>y;
_hash[x]=y;
}
memset(dp,0,sizeof(dp));//初始值dp[n]=0
for(int i=n-1;i>=0;i--)
{
if(_hash[i]!=-1)
dp[i]=dp[_hash[i]];//走捷径
else
{
for(int j=1;j<=6;j++)
dp[i]+=1.0/6*(dp[i+j]+1);//到达i的状态是由(i+1....i+6)来的
}
}
cout<<setiosflags(ios::fixed)<<setprecision(4)<<dp[0]<<endl;
}
return 0;
}
牡丹江现场赛的一道棋盘问题: http://acm.hust.edu.cn/vjudge/contest/view.action?cid=77292#problem/C
题意:n*m的棋盘,每天放置一枚棋子,求每行每列都至少有一颗棋子的期望天数。
概率dp的裸题,但是要注意细节的处理。我们设dp[i][j][k]是第k天可以控制i行j列的概率,则状态的转移,k+1天要么还是控制i行j列,要么是i+1,j或者i,j+1,或者i+1,j+1
最后的最大天数一定是n*m-min(n,m)+1。
//牡丹江棋盘问题
#include<iostream>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;
double dp[55][55][55*55];
int T,n,m;
double ans;
int main()
{
cin>>T;
while(T--)
{
cin>>n>>m;
memset(dp,0,sizeof(dp));
int pmax=n*m-min(n,m)+1;//最终需要的最大天数
dp[1][1][1]=1.0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=max(i,j);k<=i*j;k++)
{
if(i==n&&j==m)//及时跳出
break;
dp[i][j][k+1]+=dp[i][j][k]*(i*j-k)/(n*m-k);
dp[i+1][j][k+1]+=dp[i][j][k]*(n-i)*j/(n*m-k);
dp[i][j+1][k+1]+=dp[i][j][k]*(m-j)*i/(n*m-k);
dp[i+1][j+1][k+1]+=dp[i][j][k]*(n-i)*(m-j)/(n*m-k);
}
}
}
ans=0.0;
int p=max(n,m);
for(int i=p;i<=pmax;i++)
ans+=dp[n][m][i]*i;//天数*概率
cout<<setiosflags(ios::fixed)<<setprecision(12)<<ans<<endl;
}
return 0;
}
题意:给出石头、剪刀、布的个数,每次两个不同的会遇见,问经过足够长的时间后,分别单独留在场上的概率。
用dp[i][j][k]分别表示场上剩下的石头、剪刀、布的个数,根据代码中状态转移方程,最后只要场上 目标对象的 敌人为0时,求出概率和即是最终的获胜概率。
//D:
#include<iostream>
#include<iomanip>
using namespace std;
double dp[101][101][101];
int main()
{
int a,b,c;
cin>>a>>b>>c;
dp[a][b][c]=1.0;
for(int i=a;i>=1;i--)
for(int j=b;j>=1;j--)
for(int k=c;k>=1;k--)
{
if(dp[i][j][k]==0)
continue;
int tot=i*j+i*k+j*k; //总的情况
dp[i-1][j][k]+=i*k*1.0/tot*dp[i][j][k];
dp[i][j-1][k]+=j*i*1.0/tot*dp[i][j][k];
dp[i][j][k-1]+=k*j*1.0/tot*dp[i][j][k];
}
double ans=0;
for(int i=1;i<=a;i++)
for(int j=0;j<=b;j++)
ans+=dp[i][j][0];
cout<<setiosflags(ios::fixed)<<setprecision(12)<<ans;
ans=0;
for(int i=1;i<=b;i++)
for(int j=0;j<=c;j++)
ans+=dp[0][i][j];
cout<<" "<<setiosflags(ios::fixed)<<setprecision(12)<<ans;
ans=0;
for(int i=1;i<=a;i++)
for(int j=1;j<=c;j++)
ans+=dp[i][0][j];
cout<<" "<<setiosflags(ios::fixed)<<setprecision(12)<<ans<<endl;
}