【Google Code Jam】Millionaire

题目描述

Google Code Jam 2008APAC local onsites C

最开始你有X元钱,要进行M轮赌博。每一轮赢的概率为P,你可以选择赌与不赌,如果赌也可以将所持的任意一部分钱作为赌注(可以是整数,也可以是小数)。如果赢了,赌注将翻倍;输了赌注则没了。在M轮赌博结束后,如果你持有的钱在100万元以上,就可以把这些钱带回家。问:当你采取最优策略时,获得100万元以上的钱并带回家的概率是多少。

输入

M, X, P

1<=M <= 15,

1<= X <= 1000000

0.0 <= P <= 1.0

输出

获得100万以上的概率,保留6位小数

题解

每一轮的赌注是一个连续性的取值,如果想穷举赌注计算所有可能的概率是不可能的。

化连续为离散,要考虑极端情况。在最后一轮,如果拥有1000000以上的钱,就可以停止赌博,带钱回家了,这个时候带钱的概率是1。如果你有500000以上的钱,那也得全押,不然轮数已经用完了,输了就没有成功的机会了,所以成功的概率是P。

从倒数第二轮开始也是这样的做法。最后k轮带钱回家的概率跟你在倒数k轮开始的时候持有的钱是一个离散的阶梯函数,概率分别为 0, 1/(2^k+1), ... 1, 横轴是100万划分成2^k+1种情况。

只要划分100万元为2^M+1种情况,就可以将考虑的情况缩减到有限的个数(而不是根据无限的钱的可能性进行计算)。从结果倒推,每一轮中的划分情况完全靠穷举搜索找到概率最大的值,这是一个最优的子结构。不能从第1轮到第M轮,而是从M到1。

有递推公式:

  dp[i][j] = max(P * dp[i+1][j+k] + (1-P) * dp[i+1][j-k]) 0 <= k <= min(j, n - j)

最后得到的dp[0 或1 ][i]   就是从第一轮开始如果有X元, 最后得到100万元的概率(其中i= n * X / 1000000)。

复杂度是 O(M * 2^2M), M = 15的时候就有2^30次以上了。

代码

 

#include<bits/stdc++.h>
using namespace std;

const int MAXP = 15;
void solve(double dp[][1 << MAXP + 1], int M, int X, double P) {
    int n = 1 << M;
    memset(dp[0], 0, sizeof(double) * (n + 1));
    dp[0][n] = 1.0;
    int now = 0;
    for (int r = 0; r < M; r++) {
        for (int i = 0; i <= n; i++) {
            int jub = min(i, n - i);
            double t = 0.0;
            for (int j = 0; j <= jub; j++) {
                // 倒推公式
                t = max(t, P * dp[now][i + j] + (1 - P) * dp[now][i - j]);
            }
            dp[1 ^ now][i] = t;
        }
        now = 1 ^ now;
    }
    int i = (long long) n * X / 1000000;
    printf("%.6f\n", dp[now][i]);
}
int main() {
    double dp[2][1 << MAXP + 1];
    int turn;
    double money;
    int M, X;
    double P;
    while (cin >> turn >> P >> money) {
        M = turn, X = money;
        solve(dp, M, X, P);
    }
    return 0;
}

 

posted @ 2017-12-18 22:35  stackupdown  阅读(415)  评论(0编辑  收藏  举报