codejam 2008 APAC local onsites C 概率dp

链接: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;
}

你可能感兴趣的:(codejam 2008 APAC local onsites C 概率dp)