1. 问题背景与数学原理拆解
今天遇到一个有趣的概率期望问题,来自洛谷P5104的"红包发红包"题目。题目描述了一个抢红包系统:总金额为w元,n个人依次抢红包,每个人抢到的金额是在[0,当前剩余金额]区间内均匀分布的随机实数。我们需要计算第k个人期望抢到的金额,并对1e9+7取模。
这个问题的核心在于理解两个关键数学概念:期望值的计算和模运算下的除法处理。让我先解释一下背后的数学原理:
在概率论中,均匀分布是最基础的概率分布之一。对于一个在[0,x]区间内均匀分布的随机变量,它的期望值就是区间的中点x/2。这个性质很好理解——如果无数个人来抢这个红包,长期平均下来每个人抢到的金额就是当前金额的一半。
但问题来了:当第k个人抢红包时,前面的k-1个人已经抢过一轮了。根据期望的线性性质,我们可以推导出第k个人的期望金额应该是w/(2^k)。这个推导过程很有意思:
- 第1个人的期望:w/2
- 第2个人抢时,剩余金额的期望是w/2,所以他的期望是(w/2)/2 = w/4
- 以此类推,第k个人的期望就是w/(2^k)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模运算与费马小定理的应用
题目要求我们对结果取模1e9+7(这是一个质数)。在模运算中,除法不能直接进行,需要转换为乘以模逆元。这就是费马小定理大显身手的地方了。
费马小定理告诉我们:对于一个质数p和不是p的倍数的整数a,有a^(p-1) ≡ 1 (mod p)。这意味着a的逆元就是a^(p-2),因为a × a^(p-2) ≡ 1 (mod p)。
在我们的题目中,需要计算w/(2^k) mod 1e9+7,这等价于w × (2^k)^(-1) mod 1e9+7。根据费马小定理,(2^k)^(-1) ≡ (2^k)^(1e9+7-2) ≡ pow(2, k*(1e9+7-2)) mod 1e9+7。
3. 快速幂算法实现
为了高效计算大指数的模运算,我们需要使用快速幂算法。快速幂通过二分思想将O(n)的复杂度降为O(log n),这对于n可能高达1e18的情况至关重要。
快速幂的基本思想是:
- 将指数表示为二进制形式
- 利用平方的性质:a^b = (a^(b/2))^2(如果b是偶数)
- 或者a^b = a × (a^((b-1)/2))^2(如果b是奇数)
cpp复制typedef long long LL;
const int MOD = 1e9+7;
int fastPow(LL a, LL b) {
LL ans = 1;
while(b) {
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans % MOD;
}
这个实现有几个关键点:
- 使用long long防止中间结果溢出
- 每次乘法后立即取模,避免数值过大
- 通过位运算判断奇偶性和除以2,效率更高
4. 完整解题代码解析
结合上述分析,完整的解题代码如下:
cpp复制#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1e9+7;
int fastPow(LL a, LL b) {
LL ans = 1;
while(b) {
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return ans % MOD;
}
int main() {
LL w, n, k;
cin >> w >> n >> k;
cout << w * fastPow(fastPow(2, k), MOD-2) % MOD;
}
代码逻辑非常清晰:
- 读入w, n, k(注意n虽然没用上,但题目要求输入)
- 计算2^k的模逆元:fastPow(2, k)的MOD-2次方
- 将w乘以这个逆元并取模得到最终结果
5. 边界条件与注意事项
在实际编码和解题过程中,有几个容易踩坑的地方需要特别注意:
-
数据类型选择:题目中w和n的范围都很大,必须使用long long而不是int。我曾经因为没注意这个细节,导致计算结果溢出而WA。
-
模运算的时机:在快速幂中,每次乘法后都要立即取模,否则中间结果可能溢出。即使使用了long long,两个1e9级别的数相乘也会超出LL的范围。
-
费马小定理的前提条件:只有当模数是质数且底数与模数互质时才能使用。本题中MOD=1e9+7是质数,2与MOD互质,所以可以安全使用。
-
k的范围:虽然k≤n,n可以很大,但因为我们是计算2^k mod MOD的逆元,实际上k的有效范围可以缩小到k < MOD-1,因为根据费马小定理,2^(MOD-1) ≡ 1 mod MOD。
-
特殊情况的处理:当w=0时,无论k是多少结果都是0;当k=0时题目保证不会出现(k≤n且n≥1)。
6. 算法复杂度分析
让我们分析一下这个算法的时间和空间复杂度:
-
时间复杂度:主要消耗在快速幂上。快速幂的时间复杂度是O(log exponent),这里我们需要计算两次快速幂:一次是计算pow(2, k),另一次是计算pow(pow(2,k), MOD-2)。因为MOD是常数1e9+7,所以总时间复杂度是O(log k + log (MOD)) = O(log k),对于k≤1e18也完全没问题。
-
空间复杂度:只使用了常数个变量,空间复杂度是O(1)。
这个效率非常高,能够轻松处理题目给出的最大数据范围。
7. 数学证明与推导细节
为了更深入理解这个解法,让我们详细推导一下期望值的计算过程:
设总金额为w,有n个人抢红包。定义第i个人抢到的金额为X_i,剩余金额为S_i。
初始时S_0 = w。
对于第i个人:
- X_i在[0, S_{i-1}]上均匀分布
- E[X_i] = E[S_{i-1}] / 2
- S_i = S_{i-1} - X_i
- E[S_i] = E[S_{i-1}] - E[X_i] = E[S_{i-1}] / 2
通过数学归纳法:
- E[S_0] = w
- E[S_1] = w/2
- E[S_2] = w/4
- ...
- E[S_k] = w/(2^k)
因此第k个人的期望金额E[X_k] = E[S_{k-1}] / 2 = w/(2^k)
这个推导展示了期望值的线性性质如何在递推关系中发挥作用,即使每次抢红包后剩余金额是一个随机变量,期望值的计算仍然可以这样简洁地进行。
8. 实际应用与变种思考
这个问题虽然以抢红包为背景,但其核心的数学原理在很多领域都有应用:
-
资源分配问题:类似的模型可以用于分析网络带宽分配、CPU时间片分配等问题,其中资源被随机但公平地分配给多个竞争者。
-
金融衍生品定价:在期权定价中,类似的期望值计算经常出现,特别是当标的资产价格服从某种随机过程时。
-
算法设计:在随机化算法中,经常需要计算各种随机变量的期望值来评估算法性能。
这个问题的几个可能变种也值得思考:
- 如果每个人抢到的金额不是均匀分布,而是其他分布(如正态分布),期望值该如何计算?
- 如果每次抢红包不是取[0,剩余金额]的随机数,而是有最小单位(如微信红包的0.01元),问题会如何变化?
- 如果红包金额不是固定的w,而是一个随机变量,结果又会怎样?
这些变种问题可以帮助我们更深入地理解概率期望的相关概念。
9. 常见错误与调试技巧
在解决这类问题时,新手常犯的几个错误:
-
忽略模运算的除法规则:直接尝试计算w/pow(2,k)然后取模,这是完全错误的。必须转换为乘以模逆元。
-
快速幂实现错误:常见的错误包括忘记在每次乘法后取模,或者没有处理指数为0的情况。
-
数据类型溢出:即使使用了long long,在计算a*a时也可能溢出。极端情况下可能需要使用__int128或者手动实现大数乘法。
调试技巧:
- 对于小数据,手动计算验证结果
- 检查中间结果是否在预期范围内
- 使用assert语句验证不变条件
- 对于模运算,验证(a * b) % mod == (a % mod) * (b % mod) % mod等性质
10. 性能优化与替代方案
虽然当前的解法已经非常高效,但在极端情况下还可以考虑以下优化:
-
预处理2的幂次:如果需要多次查询不同的k,可以预处理出2的各个幂次及其逆元,用O(MOD)的空间换取O(1)的查询时间。
-
更快的模运算:对于固定的模数1e9+7,可以使用Barrett约减等优化技术来加速模运算。
-
并行计算:计算pow(2,k)和pow(pow(2,k), MOD-2)这两个快速幂可以并行进行。
替代方案方面,如果不使用费马小定理,还可以:
-
扩展欧几里得算法:用来直接计算模逆元,复杂度也是O(log MOD)。
-
线性递推:如果需要多次计算不同数的逆元,可以用线性时间预处理所有逆元。
但在本题的单次查询场景下,快速幂+费马小定理的组合已经是最优解了。
