2024杭电多校(7) 1002.生产机器【计数dp】

题意

某个机器在第 i i i 个小时可以生产至多 f i f_i fi 个黄色气球和 g i g_i gi 个蓝色气球,也可以不生产任何一个气球
给定接下来 n n n 个小时的生产指数 f i , g i f_i, g_i fi,gi,每个小时内生产的气球顺序可任意确定,但是前一小时生产的气球都排在后一小时生产的气球之前

计算有多少种不同的生产序列,并对 1 0 9 + 7 10^9 + 7 109+7 取模(空集也算)

1 ≤ n ≤ 1 0 5 ,    0 ≤ f i , g i ≤ 1 0 6 1 \leq n \leq 10^5, \; 0 \leq f_i, g_i \leq 10^6 1n105,0fi,gi106

思路

我们先来考虑如何检测一个序列是否合法,可以贪心地检测,即对于当前的气球,如果当前小时的这一种类的气球还有生产余量,就在这一小时生产,否则跳到下一小时。如果在 n n n 个小时内可以匹配完这个序列,那么该序列合法。

进一步观察可以发现: 这个匹配的过程和合法的序列是一一对应的关系,所以我们可以直接对匹配的过程 d p dp dp
d p i , j dp_{i, j} dpi,j 表示第 i i i 个小时生产的第一种气球颜色必须为 j ( j ∈ { 0 , 1 } ) j (j \in \{0, 1\}) j(j{0,1}),对应多少种合法方案,那么我们可以枚举另一种气球在这一小时内生产的数量,进行转移

假设当前小时内生产的第一个气球种类 j j j 最多在这一小时内生产 x x x ( x > 0 ) (x > 0) (x>0),我们先将 x − 1 x - 1 x1,因为要去掉固定为开头的那个,那么后面我们方案数为:
∑ i = 0 y ( x + i i ) = ( x + i + 1 x + 1 ) \sum_{i = 0}^{y} \binom{x + i}{i} = \binom{x + i + 1}{x + 1} i=0y(ix+i)=(x+1x+i+1)
转移到 d p i + 1 , j dp_{i + 1, j} dpi+1,j 即可

如果决定匹配过程在当前小时结束,那么方案数为:
∑ i = 0 x ∑ j = 0 y ( i + j i ) = ( x + 2 ) ( x + y + 2 x + 2 ) − y − 1 y + 1 \sum_{i = 0}^{x} \sum_{j = 0}^{y} \binom{i + j}{i} = \frac{(x + 2)\binom{x + y + 2}{x + 2} - y - 1}{y + 1} i=0xj=0y(ii+j)=y+1(x+2)(x+2x+y+2)y1

至于这个公式这么推出来的,笔者也不懂…

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

template<class T>
constexpr T power(T a, ll b){
    T res = 1;
    while(b){
        if(b&1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
    ll res = a * b - ll(1.L * a * b / mod) * mod;
    res %= mod;
    if(res < 0) res += mod; //误差
    return res;
}

template<ll P>
struct MLL{
    ll x;
    constexpr MLL() = default;
    constexpr MLL(ll x) : x(norm(x % getMod())) {}

    static ll Mod;
    constexpr static ll getMod(){
       if(P > 0) return P;
       return Mod;
    }

    constexpr static void setMod(int _Mod){
       Mod = _Mod;
    }
    constexpr ll norm(ll x) const{
       if(x < 0){
           x += getMod();
       }
       if(x >= getMod()){
           x -= getMod();
       }
       return x;
    }
    constexpr ll val() const{
       return x;
    }
    explicit constexpr operator ll() const{ 
       return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)
    }
    constexpr MLL operator -() const{ //负号,等价于加上Mod
       MLL res;
       res.x = norm(getMod() - x);
       return res;
    }
    constexpr MLL inv() const{
       assert(x != 0);
       return power(*this, getMod() - 2); //用费马小定理求逆
    }
    constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
       x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
       return *this;
    }
    constexpr MLL& operator += (MLL rhs) & {
       x = norm(x + rhs.x);
       return *this;
    }
    constexpr MLL& operator -= (MLL rhs) & {
       x = norm(x - rhs.x);
       return *this;
    }
    constexpr MLL& operator /= (MLL rhs) & {
       return *this *= rhs.inv();
    }
    friend constexpr MLL operator * (MLL lhs, MLL rhs){
       MLL res = lhs;
       res *= rhs;
       return res;
    }
    friend constexpr MLL operator + (MLL lhs, MLL rhs){
       MLL res = lhs;
       res += rhs;
       return res;
    }
    friend constexpr MLL operator - (MLL lhs, MLL rhs){
       MLL res = lhs;
       res -= rhs;
       return res;
    }
    friend constexpr MLL operator / (MLL lhs, MLL rhs){
       MLL res = lhs;
       res /= rhs;
       return res;
    }
    friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
       ll v;
       is >> v;
       a = MLL(v);
       return is;
    }
    friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
       return os << a.val();
    }
    friend constexpr bool operator == (MLL lhs, MLL rhs){
       return lhs.val() == rhs.val();
    }
    friend constexpr bool operator != (MLL lhs, MLL rhs){
       return lhs.val() != rhs.val();
    }
};

const ll mod = 1e9 + 7;
using Z = MLL<mod>;
template<>
ll MLL<0ll>::Mod = 998244353;

struct Comb {
    int n;
    std::vector<Z> _fac;
    std::vector<Z> _invfac;
    std::vector<Z> _inv;

    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {
        init(n);
    }

    void init(int m) {
        m = std::min(1ll * m, Z::getMod() - 1);
        if (m <= n) return; //已经处理完了需要的长度
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);

        for (int i = n + 1; i <= m; i++) {
            _fac[i] = _fac[i - 1] * i;
        }
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i--) { //线性递推逆元和阶乘逆元
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        }
        n = m; //新的长度
    }

    Z fac(int m) {
        if (m > n) init(2 * m);
        return _fac[m];
    }
    Z invfac(int m) {
        if (m > n) init(2 * m);
        return _invfac[m];
    }
    Z inv(int m) {
        if (m > n) init(2 * m);
        return _inv[m];
    }
    Z binom(int n, int m) { //二项式系数
        if (n < m || m < 0) return 0;
        return fac(n) * invfac(m) * invfac(n - m);
    }
} comb;

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--){
        int n;
        std::cin >> n;
        std::vector<std::array<int, 2>> a(n + 1);
        fore(i, 1, n + 1) std::cin >> a[i][0] >> a[i][1];
        std::vector<std::array<Z, 2>> dp(n + 5);
        Z ans = 1; //空序列的方案
        dp[1][0] = dp[1][1] = 1;
        fore(i, 1, n + 1){
            fore(j, 0, 2){
                auto b = a[i];
                if(!b[j]){
                    dp[i + 1][j] += dp[i][j];
                    continue;
                }
                --b[j];
                Z way = (b[0] + 2) * comb.binom(b[0] + b[1] + 2, b[0] + 2) - b[1] - 1;
                way *= comb.inv(b[1] + 1);
                ans += way * dp[i][j];

                fore(k, 0, 2){
                    dp[i + 1][k] += comb.binom(b[0] + b[1] + 1, b[k] + 1) * dp[i][j];
                }
            }
        }

        std::cout << ans << endl;
    }
    
    return 0;
}
  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值