[常中集训]小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;
}

思考总结

  1. 参数对称的式子可以裂开处理
  2. 当答案与前缀有关可以舍弃原数组
posted @ 2020-02-09 23:03  LinZhengmin  阅读(...)  评论(...编辑  收藏

Contact with me