• [常中集训]小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. 当答案与前缀有关可以舍弃原数组
  • 相关阅读:
    numpy排序函数:sort、argsort、lexsort、partition、sorted
    转载一份分类、回归、排序的评价指标
    python类的全面介绍
    好文推荐:转载一篇别人kaggle的经验分享
    实现ls -l
    C命令行参数
    C语言调用汇编
    汇编调用C程序
    linux 进程通信 :流套接字
    linux进程通信:消息队列
  • 原文地址:https://www.cnblogs.com/linzhengmin/p/12289308.html
Copyright © 2020-2023  润新知