【训练题64:组合数学 | 容斥】Product | 2021牛客暑期多校训练营4

题意

  • Product | 2021牛客暑期多校训练营4
    给你 n , k , D n,k,D n,k,D,你需要求出:
    ∑ D = ∑ a i , a i ≥ 0 D ! ∏ ( a i + k ) ! \sum_{D=\sum a_i,a_i\ge 0} \frac{D!}{\prod (a_i+k)!} D=ai,ai0(ai+k)!D!
  • 1 ≤ n ≤ 50 1\le n\le 50 1n50
    0 ≤ k ≤ 50 0\le k\le 50 0k50
    0 ≤ D ≤ 1 0 8 0\le D\le 10^8 0D108

思路

  • 对现在的我来说也是神仙题,先学着点…
    根据组合数学,我们有这个式子:
    ∑ D = ∑ a i , a i ≥ 0 D ! ∏ ( a i ) ! = n D \sum_{D=\sum a_i,a_i\ge 0} \frac{D!}{\prod (a_i)!}=n^D D=ai,ai0(ai)!D!=nD
    为什么呢?左边的式子就相当于,有 D D D 个球,放进 n n n 个箱子里,球和箱子都不同的方案数,使用的是多重集的全排列公式 (因为一个集合里放进小球 A B AB AB 与小球 B A BA BA 当然是同一种方案,多了 a i ! a_i! ai! 的方案数)
    而等式右边就是,对于每一个球,放进这 n n n 个集合中的答案都不相同,所以就是 n D n^D nD
  • 进而,我们可以修改成和题意类似的式子:
    ∑ D + n k = ∑ a i , a i ≥ 0 ( D + n k ) ! ∏ ( a i ) ! = n D + n k \sum_{D+nk=\sum a_i,a_i\ge 0} \frac{(D+nk)!}{\prod (a_i)!}=n^{D+nk} D+nk=ai,ai0(ai)!(D+nk)!=nD+nk
    但是其实题意的式子是要我们求这样子
    A n s = ∑ D + n k = ∑ a i , a i ≥ k ( D ) ! ∏ ( a i ) ! Ans=\sum_{D+nk=\sum a_i,a_i\ge k} \frac{(D)!}{\prod (a_i)!} Ans=D+nk=ai,aik(ai)!(D)!
    我们可以先稍微转换一下,变成这样:
    A n s = D ! ( D + n k ) ! ∑ D + n k = ∑ a i , a i ≥ k ( D + n k ) ! ∏ ( a i ) ! (1) Ans=\frac{D!}{(D+nk)!}\sum_{D+nk=\sum a_i,a_i\ge k} \frac{(D+nk)!}{\prod (a_i)!} \tag{1} Ans=(D+nk)!D!D+nk=ai,aik(ai)!(D+nk)!(1)
    现在的问题就是,我们要求的是 a i ≥ k a_i\ge k aik ,但是我们的式子只支持我们 a i ≥ 0 a_i\ge 0 ai0
    这个时候就要用到容斥了!(怎么又是你…)
  • 我们定义合法的方法,就是这个集合中放了 ≥ k \ge k k 个球;非法就是集合中放了 < k <k <k 个球
    d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个集合,共放了 j j j 个球,且全部都是非法集合的方案数
    这个转移是这样子的:
    d p [ i ] [ j ] = ∑ t = 0 k − 1 d p [ i − 1 ] [ j − t ] × C j t dp[i][j]=\sum_{t=0}^{k-1}dp[i-1][j-t]\times C_j^t dp[i][j]=t=0k1dp[i1][jt]×Cjt
    就是这 j j j 个球中,枚举时哪 t t t 个球放在第 i i i 个集合中
  • 接下来算我们的答案,枚举 n n n 组中有 i i i 组不合法,这 i i i 组中用了 j j j 个球,方案就是
    d p [ i ] [ j ] × C n i × C D + n k j dp[i][j]\times C_n^i\times C_{D+nk}^j dp[i][j]×Cni×CD+nkj
    那么合法的组数就是 n − i n-i ni 组,共用了 D + n k − j D+nk-j D+nkj 个球,根据我们之前的式子,方案数就是:
    ( n − i ) D + n k − j (n-i)^{D+nk-j} (ni)D+nkj
    再加上容斥的内容,还有我们 (1) 式中最左边的系数,就得到了:
    A n s = D ! ( D + n k ) ! × ∑ i = 0 n ( − 1 ) i ∑ j = 0 ( i − 1 ) k ( d p [ i ] [ j ] × C n i × C D + n k j ) × ( ( n − i ) D + n k − j ) Ans=\frac{D!}{(D+nk)!}\times \sum_{i=0}^n (-1)^i \sum_{j=0}^{(i-1)k} \Big(dp[i][j]\times C_n^i\times C_{D+nk}^j\Big) \times \Big( (n-i)^{D+nk-j} \Big) Ans=(D+nk)!D!×i=0n(1)ij=0(i1)k(dp[i][j]×Cni×CD+nkj)×((ni)D+nkj)

代码

  • 时间复杂度: O ( ( n k ) 2 ) O((nk)^2) O((nk)2) 主要是算 d p [ i ] [ j ] dp[i][j] dp[i][j] 的时候的
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e5+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

ll dp[55][3500];
ll fac[3500],ivfac[3500];

void init(int x){
    fac[0] = 1;
    for(int i = 1;i <= x;++i)fac[i] = fac[i-1] * i % MOD;
    ivfac[x] = inv(fac[x]);
    for(int i = x - 1;~i;--i)ivfac[i] = ivfac[i+1] * (i+1) % MOD;
}
ll C(int n,int m){
    if(n < 0 || m > n)return 0;
    return fac[n] * ivfac[m] % MOD * ivfac[n - m] % MOD;
}
int main()
{
    init(2500);
    ll n,k,D;cin >> n >> k >> D;

    dp[0][0] = 1;
    for(int i = 1;i <= n;++i){
        for(int j = 0;j <= (k - 1) * i;++j){
            for(int t = 0;t <= k - 1;++t){
                if(j-t>=0)dp[i][j] = (dp[i][j] + dp[i-1][j-t] * C(j,t) % MOD) % MOD;
            }
        }
    }

    ll ans = 0;

    for(int i = 0;i <= n;++i){
        ll pm = (i&1) ? -1 : 1;
        ll tmp = 1;
        for(int j = 0;j <= i * (k - 1);++j){
            ans = (ans + pm * dp[i][j] * C(n,i) % MOD * tmp % MOD * qpow(n-i,D+n*k-j) % MOD + MOD) % MOD;
            tmp = tmp * inv(j+1) % MOD * (D + n * k - j) % MOD;
        }
    }
    for(int i = 1;i <= n * k;++i){
        ans = ans * inv(D + i) % MOD;
    }
    cout << ans;
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值