题目
Paula在两张纸条上各写一个整数,这两个整数只要求彼此不同,没有其他限制。
她把两张纸分别藏在两只手里。Victor随机选一只手,Paula打开这只手,让Victor看到其中一个数。
现在Victor必须判断:他看到的这个数是两数中较大的那个,还是较小的那个。若猜对,赢\(1\)美元;若猜错,输\(1\)美元。
显然,Victor至少可以做到不输不赢(例如抛硬币决定猜“大”或“小”)。问题是:在完全不了解Paula心理、也不知道她会选什么数的前提下,Victor是否有办法把胜率提高到严格大于\(\frac{1}{2}\)?
分析
答案是:可以。
核心思路是先随机生成一个“阈值”\(T\),再用它和看到的数比较。
先在游戏开始前,Victor从某个连续分布中随机取一个实数\(T\)(例如标准正态分布)。然后采用规则:
- 若看到的数\(x>T\),就猜“这是较大的数”;
- 若看到的数\(x<T\),就猜“这是较小的数”。
设Paula写的两个整数是\(a<b\)。
- 若Victor看到的是\(a\)(概率\(\frac{1}{2}\)),他猜对当且仅当\(a\lt T\),概率为\(1-F(a)\),其中\(F\)是\(T\)的分布函数。
- 若Victor看到的是\(b\)(概率\(\frac{1}{2}\)),他猜对当且仅当\(b\gt T\),概率为\(F(b)\)。
所以总胜率
\[ P(\text{win})=\frac{1}{2}(1-F(a))+\frac{1}{2}F(b)=\frac{1}{2}+\frac{1}{2}(F(b)-F(a)).\]
因为\(a<b\)且\(T\)来自连续分布,故\(F(b)>F(a)\),于是
\[ P(\text{win})>\frac{1}{2}.\]
这说明不论Paula选哪两个不同整数,Victor都能用这个策略把期望收益做到严格为正。
C++程序
下面这个程序用蒙特卡洛方法模拟这道题。
程序固定若干组\((a,b)\)(其中\(a<b\)),并比较三种策略的胜率:
- 阈值策略(本题解法):先采样\(T\sim N(0,\sigma^2)\),若\(x>T\)猜“大”,否则猜“小”;
- 纯随机策略:等概率猜“大/小”;
- 永远猜“大”(但抽到哪只手是随机的)。
如果你想直接下载源码,可以点击这里:comparing_numbers_i_sim.cpp.txt。由于GRAV通常不会直接提供.cpp文件,这里使用可下载的文本版本;下载后把文件名改回comparing_numbers_i_sim.cpp即可。
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
#include <utility>
#include <vector>
using namespace std;
double normalCdf(double x)
{
return 0.5 * erfc(-x / sqrt(2.0));
}
double simulateThresholdStrategy(int a, int b, long long trials, double sigma, mt19937_64& rng)
{
uniform_int_distribution<int> revealDist(0, 1);
normal_distribution<double> thresholdDist(0.0, sigma);
long long wins = 0;
for (long long i = 0; i < trials; ++i)
{
int revealed = (revealDist(rng) == 0) ? a : b;
double t = thresholdDist(rng);
bool guessLarger = (revealed > t);
bool correct = (revealed == b && guessLarger) || (revealed == a && !guessLarger);
if (correct)
{
++wins;
}
}
return static_cast<double>(wins) / static_cast<double>(trials);
}
double simulateRandomGuess(long long trials, mt19937_64& rng)
{
uniform_int_distribution<int> coin(0, 1);
long long wins = 0;
for (long long i = 0; i < trials; ++i)
{
if (coin(rng) == 1)
{
++wins;
}
}
return static_cast<double>(wins) / static_cast<double>(trials);
}
double simulateAlwaysLarger(long long trials, mt19937_64& rng)
{
uniform_int_distribution<int> revealDist(0, 1);
long long wins = 0;
for (long long i = 0; i < trials; ++i)
{
if (revealDist(rng) == 1)
{
++wins;
}
}
return static_cast<double>(wins) / static_cast<double>(trials);
}
int main()
{
const long long trials = 2000000;
const double sigma = 10.0;
vector<pair<int, int>> tests = {
{-3, 3},
{0, 1},
{-8, -2},
{2, 9},
{-1, 6},
{1000, 1001}
};
random_device rd;
mt19937_64 rng(rd());
cout << fixed << setprecision(6);
cout << "Trials per test = " << trials << "\\n";
cout << "Threshold T ~ N(0, " << sigma * sigma << ")\\n\\n";
for (const auto& [a, b] : tests)
{
double empirical = simulateThresholdStrategy(a, b, trials, sigma, rng);
double fa = normalCdf(static_cast<double>(a) / sigma);
double fb = normalCdf(static_cast<double>(b) / sigma);
double theoretical = 0.5 + 0.5 * (fb - fa);
double randomGuess = simulateRandomGuess(trials, rng);
double alwaysLarger = simulateAlwaysLarger(trials, rng);
cout << "a=" << a << ", b=" << b << "\\n";
cout << " threshold strategy (empirical): " << empirical << "\\n";
cout << " threshold strategy (theory) : " << theoretical << "\\n";
cout << " random guess baseline : " << randomGuess << "\\n";
cout << " always guess larger baseline : " << alwaysLarger << "\\n\\n";
}
return 0;
}
你会看到阈值策略的胜率稳定高于\(0.5\),与前面的理论公式一致;而后两种基准策略都只能在\(0.5\)附近波动。
一个极端情况
补一个“优势极小但仍严格大于\(\frac{1}{2}\)”的例子:取\(a=1000,b=1001\),仍用上面程序中的\(\sigma=10\)。
这时理论胜率仍是
\[ P(\text{win})=\frac{1}{2}+\frac{1}{2}\bigl(F(1001)-F(1000)\bigr)>\frac{1}{2},\]
但由于\(1000,1001\)都离分布中心\(0\)极远,\(F(1001)-F(1000)\)会非常非常小,模拟结果通常会显示成\(0.500xxx\)甚至四舍五入后几乎就是\(0.5\)。
这正好说明:理论上“严格更优”与实践中“优势肉眼几乎看不见”可以同时成立。