The 2021 CCPC Weihai Onsite - M. 810975 题解



title: The 2021 CCPC Weihai Onsite - M. 810975 题解
date: 2021-11-25 19:04:21
tags: [inclusion-exclusion, combinatorics, math, FFT, team training]
mathjax: true

题意

Zayin进行了 \(n\) 场比赛, 获胜 \(m\) 局,其中最长连胜是 \(k\) ,问有多少种情况

思路

问题转化

考虑败场分割胜场(这是一个经典解法:隔板法),问题转化为

\[\sum _{i=1} ^{n-m+1} {x_i} = m \ (x_i \leq k) \]

的非负整数解个数(整数拆分问题)。

\[\begin{aligned} G(x) &= \Bigg( \sum _{i=0} ^{k} x^i \Bigg) ^ {n-m+1} \\ &= (1+x+x^2+x^3+ \cdots +x^k) ^ {n-m+1} \\ &= \Bigg(\frac{1}{1-x} - \frac{x^{k+1}}{1-x} \Bigg) ^ {n-m+1} \end{aligned} \]

TIP: 如果你打算跑NTT快速幂 / 生成函数展开,看到这里就结束了。

引理

\[\sum _{i=1} ^{p} {x_i} = m \]

的非负整数解个数为

\[C(m+p-1,\ p-1) = C ^{p-1} _{m+p-1} \]

引理证明

先考虑正整数解个数

考虑有 \(m\) 个球,分为 \(p\) 组,那么只需要这么考虑

O _ O _ O _ O _ O _ ....... _ O _ O // O 表示球, _ 表示空位

那么往空位塞入 \(p-1\) 个隔板即可,\(C(m-1,\ p-1)\)

再考虑非负整数解个数

\(y_i = x_i + 1\)

也就是考虑:先给每组球多塞一个球,作上述隔板法,就可以扩展到非负整数了

\[\begin{aligned} &\sum _{i=1} ^{p} x_i &= &\ \ m \\ &\sum _{i=1} ^{p} (x_i + 1) &= &\ \ m + p \\ &\sum _{i=1} ^{p} {y_i} &= &\ \ m + p \end{aligned} \]

因为第三个式子正整数解个数即为 \(C(m+p-1,\ p-1)\),也等于第一个式子的非负整数解个数

至此,我们得到了 \(n\) 场比赛,获胜 \(m\) 场的情况数

回到原问题

以和引理一样的思路,我们考虑某一场比赛 至少 赢了 \(k+1\) 次(因为组合的性质,我们不好解决恰好 \(k\) 次的问题),减去至少赢了 \(k\) 次就是答案了。

为了防止问题太过抽象,我们考虑 \(n=7,m=6,k=3,p=n-m+1=2\) 的情况,显然只有一种

首先,我们先抽一个 \(k\) 出来,算 \(\sum _{i=1} ^{p} {y_i} = y_1+y_2 = m - k = 3\) 情况数,然后 \(C(p,1)\) 塞给某1个 \(y_i\) ,即可保证得到的组合至少存在1个大于 \(k\) 的数。通过引理我们知道答案是 \(C(3+2-1, 2-1)\cdot C(2,1) = C(4,1) \cdot 2 = 8\)

同理,\(k+1\)也是这么算,得到答案 \(6\) ,但是 \(8 - 6 = 2 \not = 1\)

通过式子所代表的含义,我们列举出生成的 \(8\)\(k\) 的组合和 \(6\)\(k+1\) 的组合。(如果读者在这里没发现这里需要容斥,还请模拟一下8+6种情况,不会花太多时间)

发现不对,\(y_1 = y_2 = 3\) 被计入了 \(2\) 次。显然,这是因为+3计数重复。本质上,我们要求的是 y[1] >= 3 or y[2] >= 3 (记作 \(A\) ) ,但是我们这里算的是的是y[1] >= 3 + y[2] >= 3 (记作 \(B\) ),要想不重复,我们只能减去y[1] >= 3 and y[2] >= 3(记作 \(C\) )。即 \(A = B - C\),同理,我们可以推广到 p > 2 的情况,即计算

\[Ans|_{k} = \sum _{i=1} ^{\infty} (-1)^i \cdot C(n - m + 1, i) \cdot C(m - i \cdot k + n - m , n - m) \]

\(i\) 表示枚举有 \(i\) 个元素 \(\geq k\),左边那个组合数表示选 \(i\) 个数 \(+k\) ,右边那个组合数即为整数拆分非负整数解个数

\(k+1\) 同理。

最后

\[Answer = Ans|_k - Ans|_{k+1} \]

小细节

具体看代码特判

代码

#include 
using namespace std;
using i64 = long long;

const i64 MOD = 998244353;
const int N = 1e5 + 6;
i64 fac[N], inv[N], ifac[N];

void norm(i64 &x) {
    while(x>=MOD) x -= MOD;
    while(x<0) x += MOD;
}

i64 C(i64 n,i64 m) {
    if(m<0 || n> n >> m >> k;

    if(!(n>=m && m>=k)) {
        cout << 0;
        return 0;
    }
    
    if(k==0) {
        if(m==0) cout << 1;
        else cout << 0;
        return 0;
    }

    vector a(m+2), b(m+2);
    i64 w = m, f = n - m;

    i64 ans = 0;
    for(int i=1;i*k<=m;++i) {
        // k
        a[i] = C(f + 1, i) * C(m - i * k + f, f) % MOD;
        // k + 1
        b[i] = C(f + 1, i) * C(m - i * (k + 1) + f, f) % MOD;

        ans += (i&1) ? (a[i] - b[i]) : (b[i] - a[i]);
        norm(ans);
    }

    cout << ans;

    return 0;
}