杭电多校 (5) (2023-09-21)

Snake (组合数, 容斥)

题意

问将 $n$ 个编号为 $1...n$ 的小球分为 $m$ 个有序组,同时每组中球的数量不超过 $k$ 的方案数。

$1 \le m, k \le n \le 1e6$

思路

首先将所有小球进行全排列,有 $n!$ 种方案。其次考虑到各个组之间的顺序无关,得到 $ \frac{n!}{m!} $ 种方案。

接着进行隔板划分。没有数量超过 $k$ 的组的划分方案不好算,但是可以很容易的计算至少有 $x$ 组中球数至少为 $k$ 的方案数。

先将 $x \cdot k$ 个球取出,然后对剩下的 $n-x \cdot k$ 个球进行隔板划分,方案数为 $C(n-x\cdot k-1, m-1)$。然后将刚刚取出的球选 $x$ 组每组放入 $k$ 个,那么此时就至少有 $x$ 个组有至少 $k$ 个球了,这样的方案数是 $C(m, x)$。

设至少有 $x$ 组中球数至少为 $k$ 的方案数为 $f(x)$。由容斥定理可知,有 $0$ 组球数至少为 $k$ 的方案数为

$$ ans = f(0)-f(1)+f(2)-...-f(2N-1)+f(2N) (N \le m, m \le n-kN) $$
Code
#include <bits/stdc++.h>

#define all(x)      x.begin(), x.end()
#define NL          std::cout << '\n'

using lnt = long long;

signed main() {
  std::ios::sync_with_stdio(false); std::cin.tie(nullptr);
  int t;
  std::cin >> t;
  const int inf = 0x3f3f3f3f;
  const int maxN = 1e6+5;
  const int mo = 998244353;
  auto ksm = [](lnt x, lnt e) {
    lnt ret = 1;
    while (e) {
      if (e&1) { (ret *= x) %= mo; }
      e >>= 1; (x *= x) %= mo;
    }
    return ret;
  };
  std::vector<lnt> frac(maxN); frac[0] = 1;
  for (int i = 1; i < maxN; ++i) {
    (frac[i] = frac[i-1] * i) %= mo;
  }
  auto C = [&](lnt n, lnt m) {
    return frac[n] * ksm(frac[n-m] * frac[m] % mo, mo-2) % mo;
  };
  while (t--) {
    int m, k, n; std::cin >> n >> m >> k;
    lnt ret = frac[n] * ksm(frac[m], mo-2) % mo;
    lnt tmp = 0;
    for (int i = 0; i <= m && n-i*k >= m; ++i) {
      if (i&1) {
        (tmp -= C(m, i) * C(n-i*k-1, m-1)) %= mo;
      } else {
        (tmp += C(m, i) * C(n-i*k-1, m-1)) %= mo;
      }
    }
    (ret *= tmp) %= mo;
    ret < 0 ? ret += mo : 0;
    std::cout << ret; NL;
  }
  return 0;
}