Millionaire(百万赢家)

题目描述:

 样例输入/输出:

样例1:

样例2:

 

难点分析:

拿到这道题的时候,笔者觉得有一个难点。押注可以为小数 !

所以我们有难点1:连续性问题意味着情况无穷多,我们无法处理。

基于此,我们需要思考如何将问题离散化。

应对策略1:连续问题离散化

从简单简单开始思考如何离散化。以m=1(参与一轮押注)为一个例子。

如果我们手头的钱 x < 500000,那么就算我们赢得胜利,我们也不能达到目标,所以我们的胜率为0。

如果我们手头的钱 500000 <= x < 1000000;那么我们不妨全压上!因为赢了我们拿一百万回家,输了就是还给节目组。所以此时我们的获胜概率为p。

如果我们手头的钱 1000000 <= x,我们就不需要参加之后的押注,或者押注为0.因为这样子我们一定能拿到1000000。所以此时概率为1.

所以我们可以得到下面的示意图:

 

我们再来看以m = 2为一个例子看看:

如果我们2场押注全胜!那么我们至少需要250000。所以当钱 250000 <= x < 500000时,我们的胜率为p^2

反之,如果我们的钱 x < 250000时,我们毫无胜算,即胜率为0

如果我们可以败一场,那么我们需要750000的钱

当 75000 <= x < 1000000时, 我们的胜率为p(1 - p) + p;

所以我们还能看到还有一个区域 500000 <= x < 75000;这个时候我们全压的胜率就是p;

有人可能就要杠了,为什么这个时候我们输一场?我只要500000保底金额。

好吧!好吧!那我们按照“杠”的思路来走一遍。如果我们输了且保底500000,那么获胜概率为

(1 - p) * p;如果我们赢了,那么我们毫无办法!这没用我还是要参加第二轮,也就是我们的胜率为p^2。两者相加你会发现概率仍然是p。所以这所谓的“容错”是假象!换种角度说,在倒数第二轮获胜了也无法决定胜局的时候,容错几乎是毫无意义。

至此我们可以画出一下的示意图:

 

观察图我们可以发现,当经过m轮时,会产生2^m + 1段的概率值。因为m轮,每轮会产生2种可能,随后故有2^m,此后有两种理解。理解一:因为会有全败的情况将其剔除,我们加入两种情况,不参加和参加了也是无果,即胜率为1和0的情况;理解二:全败是毫无意义的游戏,它可以代表概率为0的情况,之后加入不参加的情况。所以一共有 2^m + 1段。

现在我们将连续的情况,处理成有穷段。

这个时候我们只要算出我们的概率,在对应段落落座即可。这个时候我们的第一想法就是穷竭搜索。我们每次投资最小单元k,其中k = 1000000 / n,n = 2 ^ m;

接下来,我们需要写出状态的转移方程!也就是结构化思考后计算方程。

如何思考? 其实,我们知道一个结果,就是第m的情况数是第m-1轮的两倍。

也就是说,我们在计算第m轮的时候,借用第m-1的情况即可。

因为我们需要发生进段的过程,所以最小投资单元为k。通过j * k的枚举即可。其中如果输了要在之后的过程种赢回来,那么结果是 (1 - p) * dp[i-1][i - j];如果我们赢了,在之后的过程中达到目标则为p * dp[i-1][i + j];去最佳方案后则为 t = max{t,(1 - p) * dp[i-1][i - j] + p * dp[i-1][i + j] }

如果i - j在范围内,但i + j在范围外,那么就有(1 - p) * dp[i-1][i - j] + p;我们可以知道,我们的初始钱越多,获胜概率越大!所以dp的数据理应是单调的!所以我们dp[i-1][i - j1] > dp[i-1][i - j]。其中i + j1 = n;这就表明了,当i + j 溢出 n时接下来的所有计算时无效的

当 i - j 越界,意味着我们没有更多的本金去支持我们做出决策,所以从此往后概率为0;

代码实现:

DFS

#include <iostream>
#include <algorithm>
#define ll long long

const int MAX_M = 15;
//滚动数组 -- dp[0][i]表示第i种情况的成功概率,dp[1][i]表示下一行第i中情况的成功概率
double dp[2][1 << MAX_M + 1];

void dfs(int round, int m, double p, double *crt, double *nxt) {
	if (round == m) return;
	
	int n = 1 << m;
	for (int i = 0; i <= n; ++i)
	{
		int jub = std::min(i, n - i);
		double t = 0.0;
		//投资策略每次投入j * k(k为1000000/n,最小投资额),赢则进j段,败则退j段。
		for (int j = 0; j <= jub; ++j)
		{
			//选出最佳的投入方案。
			t = std::max(t, (1 - p) * crt[i - j] + p * crt[i + j]);
		}
		nxt[i] = t;
	}
	
	dfs(round + 1, m, p, nxt, crt);
	
	return;
}

int main() {
	int n, m;
	double p, x;
	scanf("%d %lf %lf",&m, &p, &x);
	
	n = 1 << m;
	double* crt = dp[0], * nxt = dp[1];
	memset(crt, 0, sizeof(double) * (n + 1));
	crt[n] = 1.0;

	int i = (ll)x * n / 1000000;
	dfs(0, m, p, crt, nxt);

    //crt,nxt是值传入、受保护。所以最后nxt保存奇数行,crt保存偶数行
	if (m % 2 == 1) printf("%lf", nxt[i]);
	else printf("%lf", crt[i]);
	return 0;
}

递推DP:

#include <iostream>
#include <algorithm>
#define ll long long

const int MAX_M = 15;
//滚动数组 -- dp[0][i]表示第i种情况的成功概率,dp[1][i]表示下一行第i中情况的成功概率
double dp[2][1 << MAX_M + 1];

//交换指针p1、p2
void swap(double*& p1, double*& p2) {
	double * temp = p1;
	p1 = p2;
	p2 = temp;
	return;
}


int main() {
	int n, m;
	double p, x;
	scanf("%d %lf %lf",&m, &p, &x);
	
	n = 1 << m;
	double* crt = dp[0], * nxt = dp[1];
	memset(crt, 0, sizeof(double) * (n + 1));
	crt[n] = 1.0;

	//参加到第 round + 1 轮的情况
	for (int round = 0; round < m; ++round) 
	{
		//第 i 种情况的成功概率
		for (int i = 0; i <= n; ++i)
		{
            //有效计算范围
			int jub = std::min(i, n - i);
			double t = 0.0;
			//投资策略每次投入j * k(k为1000000/n,最小投资额),赢则进j段,败则退j段。
			for (int j = 0; j <= jub; ++j)
			{
				//选出最佳的投入方案。
				t = std::max(t, (1 - p) * crt[i - j] + p * crt[i + j]);
			}
			nxt[i] = t;
		}
		swap(crt, nxt);
	}

	int i = (ll)x * n / 1000000;
    
    //crt始终表示我们正在处理的行数据
	printf("%lf", crt[i]);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值