millionaire
题意:最开始你有x元钱,要进行M轮赌博。每一轮赢的概率为P,你可以选择赌与不赌,如果赌也可以将所持的任意一部分钱作为赌注(可以是整数,也可以是小数)。如果赢了,赌注将翻倍;输了赌注则没了。在M轮赌博结束后,如果你持有的钱在100万元以上,就可以把这些钱带回家。问:当你采取最优策略时,获得100万元以上的钱并带回家的概率是多少。
思路: 将最后一场赌局开始时的钱数分成三段,0~500000,500000~1000000,1000000以上。第一段赢得概率为0,第二段为P,第三段为1。 依此类推,第一局分段最多,为2^M+1。因此,可将钱数分为2^M+1段,任何一局每一段内的赢率相同。从最后一局开始向前递推,每局开始时钱数为j段,结束时为j+k(赢)或j-k(输),概率为P和1-P。
动态方程:dp[i][j] = max(dp[i][j], P*dp[i+1][j+k] + (1 - P)*dp[i+1][j-k]) dp[i][j]为第i场赌局开始时钱数为j段的情况下赢的概率。
由于每一次概率的更新只与下一场赌局(i+1)有关,所以可以设两个一维数组交换进行,可以节省内存。 动态方程:st[j] = max(st[j], P*en[j + k] + (1 - P)*en[j - k])
#include <iOStream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
double en[1<<15 + 2]; //第i场结束时拥有的钱数在j区间 double st[1<<15 + 2]; // 第i场开始时拥有的钱数在j区间
int main() {
int M, X, N; double P; scanf("%d", &N);
for(int a = 1; a <= N; a++){
scanf("%d %lf %d", &M, &P, &X);
int n = 1<<M; //n = 2^M,将1000000分成n+1个区:0~1000000/n,1000000/n~2*1000000/n...(n-1)*1000000/n~1000000,000000以上 若钱数>=1000000可直接带走,概率1
memset(en, 0, sizeof(en)); memset(st, 0, sizeof(st)); en[n] = 1; //若最后一局结束时钱数在n区间则必赢
for(int i = M; i > 0; i--){ //第i场赌赛(倒着算)
for(int j = 0; j <= n; j++){ //本场赌局开始时我拥有的钱数在j区间,这种情况下我最终能赢概率为st[j]
int temp = min(j, n - j); //temp为本轮赌局结束时我最多拥有的钱数 //保证2*j <= n,因为钱数到n区间可直接拿走,超出没有意义,若输掉赌局可能失去更多钱,不是最优解
st[j] = 0.0; for(int k = 0; k <= temp; k++){ //下一轮赌局拿出k区间的钱数参赌
st[j] = max(st[j], P*en[j + k] + (1 - P)*en[j - k]); //若赢,钱数 = j - k + k*2 = j + k //若输,钱数 = j - k
}
} swap(en, st); //本场赌局开始的钱数和上一场赌局结束的钱数相同
} int i = (long long)X*n/1000000; //最初我拥有的钱数在i区间
printf("Case #%d: %.6f\n", a, en[i]);
} return 0;
}