首页 > 其他 > 详细

[常中集训]小B的夏令营

时间:2020-02-09 23:38:29      阅读:141      评论:0      收藏:0      [点我收藏+]

前言

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. 当答案与前缀有关可以舍弃原数组

[常中集训]小B的夏令营

原文:https://www.cnblogs.com/linzhengmin/p/12289308.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!