题解 | 小红的小球染色期望
小红的小球染色期望
https://www.nowcoder.com/practice/a91dc04fffe84a939e5258febaaa9044
题目信息
- 题目编号: ACM359
- 题目名称: 小红的小球染色期望
- 难度: 1800(较难)
一、DP方程定义
状态定义
dp[i] 表示有 i 个白色小球时,小红操作次数的期望值。
边界条件
dp[0] = dp[1] = 0:只有0个或1个球时,只能操作0次。
dp[2] = 1:只有2个球时,只能操作1次。
状态转移方程
对于 n 个球,小红第一次有 (n-1) 种选择(选择相邻的两个球):
- 如果选择位置
i和i+1的两个球(1 ≤ i ≤ n-1) - 染色后,这两个球变红,将原序列分成两个独立的部分: 左边:i-1 个白球;右边:n-i-1 个白球;中间的红球将两边隔开,互不影响
每种选择的概率为 1/(n-1),所以:
这里:
1表示当前这次操作- 后面的求和表示剩余两部分的期望操作次数
二、复杂度优化
优化前(暴力):O(n²)
如果直接按上面的公式计算,每个 dp[i] 需要遍历所有可能的分割点,总复杂度为 O(n²)。
优化后:O(n)
观察求和式:
关键优化:引入前缀和数组 sum[i] = dp[0] + dp[1] + ... + dp[i]
则转移方程简化为:
这样每个状态只需要 O(1) 时间计算,总复杂度降为 O(n)。
代码实现:
dp[i] = sum[i-2] * 2 % mod * inv(i-1) % mod + 1; sum[i] = (sum[i-1] + dp[i]) % mod; // 维护前缀和
三、概率论和逆元技巧
1. 期望的线性性质
期望满足线性性质:
这道题中,总期望 = 当前操作(1次) + 左边期望 + 右边期望。
2. 模意义下的除法 = 乘以逆元
在模运算中,a / b (mod M) 需要转化为 a × b⁻¹ (mod M)。
3. 费马小定理求逆元
当模数 M = 10⁹ + 7 是质数时,根据费马小定理:
因此:
代码实现快速幂:
int power(int a, int b) {
int res = 1;
while(b) {
if(b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
int inv(int x) {
return power(x, mod - 2); // x的逆元 = x^(mod-2)
}
4. 取模运算的注意事项
由于涉及多次乘法,需要每步都取模防止溢出:
dp[i] = sum[i-2] * 2 % mod * inv(i-1) % mod + 1;
计算顺序:(sum[i-2] * 2) % mod,然后乘以 inv(i-1),再模,最后加1。
完整AC代码
#include <iostream>
using namespace std;
#define int long long
int dp[2020200], sum[1010100];
const int mod = 1e9 + 7;
int power(int a, int b) {
int res = 1;
while(b) {
if(b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
int inv(int x) {
return power(x, mod - 2);
}
signed main() {
int n, i;
cin >> n;
dp[2] = sum[2] = 1;
for(i = 3; i <= n; i++) {
dp[i] = sum[i-2] * 2 % mod * inv(i-1) % mod + 1;
sum[i] = (sum[i-1] + dp[i]) % mod;
}
cout << dp[n] % mod;
}
总结
这道题的精髓在于:
- DP建模:将期望问题转化为递推关系
- 前缀和优化:从 O(n²) 优化到 O(n)
- 逆元技巧:在模运算下正确处理除法运算
时间复杂度:O(n log mod)(主要是计算逆元的快速幂)
空间复杂度:O(n)
关键要点
- 期望DP的核心:分情况讨论,每种情况的期望 × 概率,然后求和
- 前缀和优化:发现求和式的对称性,避免重复计算
- 逆元计算:费马小定理在质数模下求逆元
- 模运算:每步都要取模,防止溢出
360集团公司氛围 352人发布