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;
}

<think>好的,我现在需要解决用户关于蓝桥杯大赛中“百万富翁问题”的C++解法请求。首先,我得明确这个问题的具体内容。可能用户没有详细描述题目,所以我得先回忆或查找蓝桥杯历届试题中的相关题目。 假设“百万富翁问题”是指一个典型的交换金钱的问题,例如陌生人每天给富翁10万元,而富翁第一天给1分,第二天2分,第三天4分,以此类推,持续30天。需要计算双方各自的总金额,或者判断谁更划算。这类问题通常涉及指数增长的计算,比如等比数列求和。 接下来,我需要确认这个问题的数学模型。富翁每天支付的是2^(n-1)分,n从1到30天。而陌生人每天给10万元,共30天。计算两者的总和并进行比较。 用户需要C++的解法,因此需要编写代码来计算这两个总和。可能的步骤包括: 1. 计算陌生人总共支付的钱:30天×10万/天=300万,这部分简单。 2. 计算富翁支付的总和:这是一个等比数列求和,首项a=1分,公比r=2,项数30。总和S=a*(r^n -1)/(r-1)。但需要注意数据类型的溢出问题,因为2^30会很大,超过普通整型的范围。因此必须使用大整数类型或者转换为更大的数据类型,如unsigned long long。 然后,编写C++代码时,需要注意分和元的转换。例如,富翁的总支付是分,最后可能需要转换成元或其他单位。此外,输出结果要清晰。 可能的代码结构包括循环累加富翁每天的金额,或者直接使用等比数列公式计算。循环方法更直观,但需要确保不会溢出。例如,使用unsigned long long类型,其最大值是18446744073709551615,而2^30是1073741824分,总和是2^30 -1分,即1073741823分,约为1073万元。因此,用unsigned long long足够存储。 然后,编写代码示例,并进行测试。同时,需要处理单位转换,比如分到元的转换,可能需要除以100,但要注意是否是整数转换的问题。 此外,可能用户的问题还有其他变种,比如计算到第几天富翁的支出超过陌生人等。需要确认题目的具体要求,但假设题目是计算总和,那么代码应输出两者的总金额,并比较谁更多。 最后,生成相关问题,可能涉及算法优化、数据类型选择、等比数列的应用等。</think>### 蓝桥杯“百万富翁问题”C++解法 #### 问题描述 假设富翁与陌生人达成协议:陌生人每天给富翁$10$万元,而富翁第一天支付$1$分钱,第二天$2$分,第三天$4$分……每天翻倍,持续$30$天。需要计算双方最终收支差[^1]。 #### 解题思路 1. **数学模型**:富翁的支付构成等比数列,首项$a=1$分,公比$r=2$,总天数$n=30$,总金额为: $$S = \frac{a(r^n - 1)}{r - 1} = 2^{30} - 1 \text{分}$$ 2. **数据类型**:因$2^{30} \approx 10.7$亿分,需使用`unsigned long long`避免溢出。 3. **单位转换**:将分转换为元($1$元$=100$分)。 #### C++代码实现 ```cpp #include <iostream> #include <cmath> using namespace std; int main() { // 陌生人总支付:30天 × 10万/天 = 300万 const unsigned stranger_total = 30 * 100000; // 单位:元 // 富翁总支付:2^30 - 1 分,转换为元 unsigned long long millionaire_total_cents = pow(2, 30) - 1; unsigned long long millionaire_total_yuan = millionaire_total_cents / 100; cout << "陌生人总支付:" << stranger_total << " 元" << endl; cout << "富翁总支付:" << millionaire_total_yuan << " 元" << endl; cout << "差额:" << stranger_total - millionaire_total_yuan << " 元" << endl; return 0; } ``` #### 代码说明 - 陌生人总收入为固定值$300$万元。 - 富翁的支付通过等比数列公式直接计算,避免了循环累加的时间复杂度$O(n)$。 - 使用`pow(2,30)`计算指数时需注意浮点精度问题,实际可通过位运算优化(例如`1ULL << 30`)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值