[清华集训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;
}