最近练习概率DP和计算的还是有用的啊,知道经典思路是什么了
题目链接:HDOJ5781
说说题意吧:有一本存折,忘记了有多少钱,只记得钱数不超过K
现在有M次猜测的机会,必须要确定存折中有多少钱!
确定一个词,理解好了,这个题就好了。这样理解确定:
如果现在钱数不超过T,取一块钱都取不出来了,那么就确定存折里面没有钱了
如果现在钱数为1,取1块钱取出来了,那么就确定存折里面没有钱了(因为所有钱都已经取出来了)
确定一词,是用来确定边界情况的
所以,这个题的方法就是枚举:枚举所有的可能猜测的值(从1枚举到K),当然,有的取得出来,有的取不出来
日常概率的经典做法就是分类:
dp【i】【j】为现在钱数不超过i,还有j次猜测的情况下的期望
如果假设当前情况下钱数我要取K,那么有两种情况:
取得出来:说明钱数上限比K大,那么是由dp【i-k】【j】过来的,概率是(i-k+1)/(i+1),分子是说k,k+1,k+2,……i,都符合,分母是说0,1,2,……i
取不出来:说明钱数上限不足K,那么上限为K-1(还是不确定有多少钱),用过了一次猜测的机会,概率是k/(i+1),那么是由dp【k-1】【j-1】过来的
k是多少呢?不知道,所以需要枚举
这样看,i,j,k三个变量,三重循环,都是需要枚举的,复杂度是O(K^2*W),会超时
那么,想想,W是不需要那么搞的,因为最好的方法是二分,就可以减去一半的枚举量,也就是说:
最多的枚举次数是:O(logK),K最大是2000,那么j最大是12咯
所以可以用上面的方法打表,而且减小时间复杂度
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int maxw=12;
const int mod=1e9+7;
double dp[2333][15];
const double eps=1e-8;
const double INF=1e9;
int main(){
//freopen("input.txt","r",stdin);
for(int i=0;i<=2000;i++)
for(int j=0;j<=maxw;j++){
dp[i][j]=INF;
if (i==0) dp[i][j]=0;
else if (j>0){
for(int k=1;k<=i;k++)
dp[i][j]=min(dp[i][j],(i+1-k)*1.0/(i+1)*dp[i-k][j]+k*1.0/(i+1)*dp[k-1][j-1]);
dp[i][j]+=1;
}
}
int k,w;
while(scanf("%d%d",&k,&w)!=EOF){
w=min(w,maxw);
printf("%.6lf\n",dp[k][w]);
}
return 0;
}