The 2021 CCPC Weihai Onsite M

Link:https://codeforces.com/gym/103428/problem/M

知识点:组合数学,容斥

简述

给定整数 \(n, m, k\),求满足如下条件的 01 串的数量:

  • 长度为 \(n\)
  • 1 的个数为 \(m\)
  • 最长的连续全 1 段的长度为 \(k\)

\(0\le n,m,k\le 10^5\)
2S,256MB。

分析

首先把限制 \(k\) 搞掉,记 \(f(x)\) 为仅允许有长度不大于 \(x\) 的全 1 段的方案数,则限定最长段为 \(k\) 的方案数即为 \(f(k) - f(k-1)\)

然后考虑如何求 \(f(k)\)。限定串中有 \(m\) 个 1,即有 \(n-m\) 个 0,则构造字符串等价于在 \(n-m\) 个 0 之间和两端共 \(n-m+1\) 个空中填入总共 \(m\) 个 1,每个空可填入 \(0\sim k\) 个 1。这是个经典问题,参考 hdu6397,考虑容斥消去填入上限的限制。

\(g(i)\) 表示总共填入了 \(m\) 个 1 且没有填入上限,有至少 \(i\) 个空至少填入了 \(k+1\) 的方案数:

  • 显然有 \(0\le i\le \min\left( n - m + 1, \frac{m}{(k + 1)}\right)\)
  • 对于 \(i=0\),即每个空没有填数上限,则直接插板法,方案数为 \({{(n-m+1) + m - 1} \choose {n-m-1 + 1}} = {n\choose {n-m}}\)
  • 对于 \(i>0\),考虑先选出 \(i\) 个空为它们预分配 \(k+1\),然后转化为了 \(i=0\) 的情况,方案数为 \({{n-m+1}\choose i}\times {{n - (k+1)\times i} \choose {n-m}}\)

则有 \(f(k) = \sum\limits_{i} (-1)^i\times g(i)\),注意特判 \(f(-1) = 0\)

预处理下阶乘和逆元,总时间复杂度 \(O(n)\) 级别。

代码

//知识点:组合数学,容斥
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const LL p = 998244353;
//=============================================================
int n, m, k;
LL inv[kN], fac[kN], ifac[kN];
//=============================================================
LL C(LL n_, LL m_) {
  if (m_ > n_) return 0;
  return fac[n_] * ifac[m_] % p * ifac[n_ - m_] % p;
}
void Init() {
  inv[1] = fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
  for (int i = 2; i < kN; ++ i) {
    inv[i]= 1ll * (p - p / i + p) % p * inv[p % i] % p;
    fac[i] = fac[i - 1] * i % p;
    ifac[i] = ifac[i - 1] * inv[i] % p; 
  }
}
LL f(LL k_) {
  if (k_ == -1) return 0;

  LL ans = 0, f = 1;
  for (int i = 0; i <= std::min(n - m + 1ll, m / (k_ + 1)); ++ i, f = -f) {
    LL d1 = C(n - m + 1, i), d2 = C(n - (k_ + 1) * i, n - m);
    ans = (ans + f * d1 * d2 % p + p) % p;
  }
  return ans;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  Init();
  std::cin >> n >> m >> k;
  std::cout << (f(k) - f(k - 1) + p) % p << "\n";
  return 0;
}
posted @ 2024-02-29 18:44  Luckyblock  阅读(9)  评论(0编辑  收藏  举报