题目大意
开局持有本钱 X,进行 M 局赌局,每局有 P 的概率使所下赌注翻倍,1-P 的概率赌注全输,押注没有要求,可以全压或者不压,也可以压任意数量包括小数的赌注,要求在赌局结束时如果持有10万元及以上的钱就可以获胜。计算在采取最优策略时,获胜的概率。
输入
按照 M P X 的顺序输入。
1,0.5,500000
输出
0.50000
分析
由于赌注的押注是任意的,这意味着有无限种可能性,表面看来无法利用穷举,不妨先进行几次模拟来探寻一下规律。
当只进行一轮时,本金与获胜关系为:
①X >=100万,必胜,概率 = 1;
②100万 >= X >= 50万 ,刚好获胜的情况是,设投入 Y (Y <= X )万元,则 (X-Y) + 2Y = 100 → X + Y = 100 , 当X = 50 时,Y = 50 概率 = P;当X > 50 时,只要至少投入100 - X 万元 ,那么一定有P的概率获胜,所以在这个区间内,获胜的最优概率都是一样的。(实际上就是连续数学的离散化,举几个例子就能确认)。
③50万 >= X >= 0,就算全押入,也必输,概率 = 0。
发现在一定区间内,本金与获胜概率存在对应关系。
当只进行二轮时,本金与获胜关系为:
先不急讨论,来看一下,不管第一轮本金多少,是输了还是赢了,最后的所持钱数仍然会属于进行一轮时的三个区间内,那么就可以得到一个倒推关系:若上一轮的押注翻倍,或者失败,则下一轮的本金 = 上一轮本金 ± 押注 ,而下一轮的本金与获胜关系的概率已经求出,那么 可以得到 该轮本金与获胜关系 = P * 下一轮本金与获胜概率(1) + (1-P) * 下一轮本金与获胜概率(2)。
(1) 表示 上一轮押注获胜 ,下一轮本金 = 上一轮本金 + 押注 存在自己的获胜概率P1,那么总概率为 P * P1;
(2) 表示 上一轮押注失败, 下一轮本金 = 上一轮本金 - 押注 存在自己的获胜概率P2,那么总概率为 (1-P) * P2;
举个例子:第一轮有本金 X = 75 万,若投入 25 万,那获胜概率为 (押注赢)P* (下一轮本金所在区间的概率)P(75 + 25);
或者 押注失败(1 - P)* (下一轮本金所在区间的概率)P(75 - 25)。
P(75) = P * P(75 + 25) + (1-P) * P(75 - 25) (两种情况的概率是相加,因为都属于75进行的押注操作)
那么显然,有了递推关系后可以考虑动态规划,而且我们可以发现 进行轮数 M 与 本金所分成的区间关系为 n = 2^M + 1;
规定 dp[i][j] 表示第 i 轮 本金处于第 j 区间的获胜概率 ,则 dp[i][j] = P * dp[i][j + k] + (1 - P) * dp[i][j - k]。 0 =< k <= min(i,n-i).
k其实表示押注后,若赢钱,那么相当于本金所处的区间变化了,并且是向本金区间增大或者减小的方向变化,且变化的上下限不超过min(i,n-i) (防止数组越界),注意要从0开始,及0的时候表示什么都不押。
下面给出代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 15;
int m,x;
double p;
double dp[2][(1<<maxn)+1];
void solve(){
int n = (1<<m) + 1;
double *prv = dp[0], *nxt = dp[1];
memset(prv,0,sizeof(double)*(n+1));//表示进行0轮,也就是持有本钱时,不进行押注直接判胜负,作为用来推出进行1轮的预处理。
prv[n] = 1.0;//持有大于等于指定获胜金额的区间的获胜概率必然为1
//进行M轮
for (int r = 1; r <= m; ++r){
//对每一个下一轮本钱区间进行上一轮的区间递推
for(int i = 1; i <= n; i++){
int step = min(i,n-i);
double t = 0.0;
//进行押注,本钱区间进行变化,递推出下一轮的dp
for(int j = 0; j <= step; j++){
t = max(t,p*prv[i+j]+(1-p)*prv[i-j]);
}
nxt[i] = t;
}
/*输出每一次的prv与nxt进行更直观的检查
for(int i = 1; i <= n; i++){
cout<<"prv = "<<prv[i]<<" ";
}
cout<<endl;
for(int i = 1; i <= n; i++){
cout<<"nxt = "<<nxt[i]<<" ";
}
cout<<endl;
cout<<endl;*/
//因为进行i+1轮只与进行i轮的概率相关,所以可以只采用两个数组,当i+1求得时,i可以废弃,作为进行i+2轮的储存单元。
swap(prv,nxt);
}
//仍然是检查
/*for(int i = 1; i <= n; i++){
cout<<"prv = "<<prv[i]<<" ";
}
cout<<endl;
for(int i = 1; i <= n; i++){
cout<<"nxt = "<<nxt[i]<<" ";
}*/
return;
}
void input(){
cin>>m>>p>>x;
return;
}
int main()
{
input();
solve();
return 0;
}