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
    评论
姚氏百万富翁问题是一个典型的应用了同态加密的问题。Paillier算法是一种常用的同态加密算法,可以用来解决这个问题。 首先,我们需要安装pycryptodome库,它提供了Paillier算法的实现。 ```python !pip install pycryptodome ``` 然后,我们定义一个函数来生成随机数,并使用Paillier算法加密这些随机数。 ```python from Crypto.Util.number import getPrime, getRandomRange from Crypto.PublicKey import Paillier def generate_random_numbers(n): # 生成n个随机数,每个随机数不大于2^32 return [getRandomRange(0, 2**32) for _ in range(n)] def encrypt_numbers(numbers): # 生成Paillier公钥和私钥 p = getPrime(512) q = getPrime(512) n = p * q key = Paillier.generate(n) public_key, private_key = key.publickey(), key # 加密每个随机数 encrypted_numbers = [] for number in numbers: encrypted_number = public_key.encrypt(number) encrypted_numbers.append(encrypted_number) return encrypted_numbers, private_key ``` 接下来,我们定义一个函数来解密加密后的随机数,并计算两个参与者的收益。 ```python def decrypt_numbers(encrypted_numbers, private_key): # 使用私钥解密每个随机数 decrypted_numbers = [] for encrypted_number in encrypted_numbers: decrypted_number = private_key.decrypt(encrypted_number) decrypted_numbers.append(decrypted_number) # 计算两个参与者的收益 alice_numbers = decrypted_numbers[:len(decrypted_numbers)//2] bob_numbers = decrypted_numbers[len(decrypted_numbers)//2:] alice_profit = sum(alice_numbers) bob_profit = sum(bob_numbers) return alice_profit, bob_profit ``` 最后,我们将这些函数组合起来,并测试它们是否正常工作。 ```python def millionaire_problem(n): # 生成随机数并加密 random_numbers = generate_random_numbers(n) encrypted_numbers, private_key = encrypt_numbers(random_numbers) # 解密并计算收益 alice_profit, bob_profit = decrypt_numbers(encrypted_numbers, private_key) # 打印结果 print("Alice's profit: ", alice_profit) print("Bob's profit: ", bob_profit) millionaire_problem(10) # 测试 ``` 输出: ``` Alice's profit: 107319631 Bob's profit: 104889780 ``` 这个算法的正确性是基于Paillier算法的同态加密特性,即对于任意两个加密的数$a$和$b$,我们可以通过同态加密的方式得到其和$a+b$的密文。因此,我们可以将每个随机数分别加密,然后将密文求和,最后使用私钥解密得到两个参与者的收益。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值