[常中集训]小B的夏令营
前言
CF原题,但是好题。
题解
略,见代码注释
具体就是前缀和上前缀和再前缀和。
然后架空原数组。
代码
#include <cstdio>
#define min(a, b) (a<b?a:b)
#define ll long long
#define MOD 1000000007
using namespace std;
ll ans, g[3005], fc[100005], inv[100005];
ll f[2][3005][3005], preg[100005], p[100005][2];
ll ksm(ll a, ll b){
ll ans = 1;
for ( ; b; b >>= 1, a = a * a % MOD)
if (b & 1) ans = ans * a % MOD;
return ans;
}
// 递推预处理阶乘和阶乘的逆元
inline void getFac(int k){
fc[0] = 1;
for (int i = 1; i <= k; ++i)
fc[i] = fc[i - 1] * i % MOD;
inv[k] = ksm(fc[k], MOD - 2);
for (int i = k - 1; ~i; --i)
inv[i] = inv[i + 1] * (i + 1) % MOD;
}
// 组合数计算,递推预处理优化至 O(1)
ll getC(ll x, ll y){
return (fc[x] * inv[y] % MOD) * inv[x - y] % MOD;
}
int main(){
ll n, m, A, B, k;
scanf("%lld %lld %lld %lld %lld", &n, &m, &A, &B, &k);
getFac(k);
ll pAB = A * ksm(B, MOD - 2) % MOD; // 计算风每天摧毁房子的概率
for (int i = 0; i <= min(k, m); ++i){
// g[i] 表示某行破坏 i 列房子的概率
// k 天中选出 i 天的组合数 乘以 这 i 天每天都刚刚好正中 pAB 的概率被摧毁的概率
// 再乘以剩下的 (k - i) 天都不被摧毁的概率
g[i] = getC(k, i) * ksm(pAB, i) % MOD
* ksm((1 - pAB + MOD) % MOD, k - i) % MOD;
}
preg[0] = g[0]; // 生成 g 概率的前缀和
for (int i = 1; i <= m; ++i)
preg[i] = (preg[i - 1] + g[i]) % MOD;
f[0][0][0] = f[1][0][0] = 1;
for (int i = 1; i <= n; ++i){ // 前 i 行
for (int l = 0; l < m; ++l){ // 剩余区间为 (l, ...)
f[0][i][l] = -p[m - l - 1][0], f[1][i][l] = -p[m - l - 1][1]; // 由带系数的 sum_dp 前缀和转移而来
// 计算 dp_r 与 dp_l
f[0][i][l] = ( (f[0][i][l] +
(f[0][i - 1][0] - f[1][i - 1][m - l]) * preg[m - l - 1]) % MOD
+ MOD) % MOD;
f[1][i][l] = ( (f[1][i][l] +
(f[0][i - 1][0] - f[0][i - 1][m - l]) * preg[m - l - 1]) % MOD
+ MOD) % MOD;
f[0][i][l] = f[0][i][l] * g[l] % MOD;
f[1][i][l] = f[1][i][l] * g[l] % MOD;
}
for (int r = m - 1; ~r; --r){
// 维护 sum_f_l 与 sum_f_r 的前缀性
(f[0][i][r] += f[0][i][r + 1]) %= MOD;
(f[1][i][r] += f[1][i][r + 1]) %= MOD;
}
for (int r = 0; r <= m; ++r){
// 根据对称性计算乘上系数的概率
p[r][0] = g[r] * f[0][i][m - r] % MOD;
p[r][1] = g[r] * f[1][i][m - r] % MOD;
}
for (int r = 1; r <= m; ++r){
// 计算概率 p 的前缀和
(p[r][0] += p[r - 1][0]) %= MOD;
(p[r][1] += p[r - 1][1]) %= MOD;
}
}
// 答案显然已经计算完毕
printf("%lld", f[0][n][0]);
return 0;
}
思考总结
- 参数对称的式子可以裂开处理
- 当答案与前缀有关可以舍弃原数组