【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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通