[清华集训2016] 你的生命已如风中残烛

简化题意

\(w_i\) 全部减 \(1\),则问题变成一个序列,其中给定有 \(n\) 个正数 \(w_i\),其余都是 \(-1\),和是 \(0\),前缀和均非负,求排列方案数。

做法

如果和是 \(1\),且要求前缀和全为正数,有结论:每个排列的所有循环移位只有 \(1\) 种合法,所以计数圆排列数即可,《具体数学》7.5 章有详细证明,其实就是每次必须要取最后一个最小值位置作为起点,使得其他最小值位置跨过序列结尾,前缀和为 \(1\),否则如果有两个相等最小值而取前一个作为起点,第二个最小值位置前缀和是 \(0\)

本题前缀和可以为 \(0\),因此每个最小值都能取,分析失效。

证 Catalan 数时,可以在开头放个 \(1\),但本题中有多种不等价的正数,这种做法是无效的。

在结尾放个 \(-1\),问题变成前 \(m\) 个元素前缀和非负,且总和为 \(-1\)

类似地,所有循环移位中现在只能取第一个最小值位置作为起点了,否则跨过序列结尾,之前的最小值位置前缀和会变成 \(-1\)

\(m+1\) 个点共有 \(m!\) 种圆排列,而序列中有 \(m - n + 1\)等价\(-1\) 只能取额外放置的 \(1\) 个放在结尾(结尾一定是 \(-1\)),因此答案 \(\displaystyle\frac{m!}{m - n + 1}\)

P.S. 对于第一个结论,如果和为 \(s\),那么有 \(s\) 种循环移位。

#include <cstdio>

int main() {
  int n, m = 0, s = 1, x;
  scanf("%d", &n);
  for (int i = 1; i <= n; ++i) {
    scanf("%d", &x);
    m += x;
  }
  for (int i = 2, li = m - n; i <= li; ++i)
    s = 1LL * s * i % 998244353;
  for (int i = m - n + 2; i <= m; ++i)
    s = 1LL * s * i % 998244353;
  printf("%d\n", s);
  return 0;
}
posted @ 2020-09-04 22:53  RiverHamster  阅读(232)  评论(0编辑  收藏  举报
\