链接:https://code.google.com/codejam/contest/32005/dashboard#s=p2
概率dp着实不懂。。只能学习。
解法:
首先发现这个问题是一个连续的问题,因为你的赌注是任意的,还可以是小数。但是可以从最简单的情况入手来分析。
从只有一轮的时候分析,1)如果手上直接有100w的钱就有1的概率可以带回家,2)如果手上小于50w的钱,那么怎么样都不可能可以带回家,概率为0。3)如果在50w~100w之间的话,可以将钱全部押下去,赢了就可以带回家,输了就不能。概率为p。
这时候在分析一下有两轮的情况。1)如果小于25w概率为0。2)如果在25w到50w之间,发现可以归约到上面只有一轮时的第三种情况,那么就是将手上的钱投下去,如果翻倍了就变成上面的第三种情况,再投下去,如果赢了那么就可以带回家。概率p*p。
此时可以发现对于上面的每种情况(除了直接带回家的情况)都可以分成两种小情况归约到上面。
50w~100w也可分为两种情况,50~75,75~100。
这样总共就有5种情况,发现对应有m轮,就会有
2m+1
这么多种情况。
在某一情况范围内的概率都是一样的,不管具体的钱数是多少,成功将连续转换成离散。
对于每一种情况都可以从上一轮的一些情况中转移过来。此时需要一个滚动数组,记录上一轮和当前轮的。定义dp[2][],对于每种情况遍历所有的可能,求出其中的最大值,就是当前情况的最大概率
学到的方法:对于难题找不到入手点的时候,尽可能的选择最简单的情况开始分析,再看稍微复杂一点的,思考是否能归约到更简单的情况下来。避免一点思绪都没有。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 1000000
typedef long long ll;
int n;
int m,x;
double p;
double dp[2][1<<15+1];
void slove()
{
n = 1 << m;
fill(dp[0],dp[0]+n,0);
fill(dp[1],dp[1]+n,0);
dp[1][n] = 1.0;
for(int k = 0;k < m;k++) //枚举第几轮
{
for(int i = 0;i <= n;i++) // 枚举情况
{
double t = 0.0;
for(int j = 0;;j++) //遍历所有可能
{
if(i+j > n || i-j < 0) break;
t = max(t,p*dp[(k+1)&1][i+j]+(1-p)*dp[(k+1)&1][i-j]);//运用滚动数组
}
dp[k&1][i] = t;
}
}
int ans = (ll)x * n / M; //判断在哪一个范围中
printf("%.6f\n",dp[(m-1)&1][ans]);
}
int main()
{
freopen("C-large-practice.in","r",stdin);
freopen("out1.txt","w",stdout);
int t;
scanf("%d",&t);
int kase = 1;
while(t--)
{
scanf("%d %lf %d",&m,&p,&x);
printf("Case #%d: ",kase++);
slove();
}
return 0;
}