題意就不用講了吧……

雞你太美!!!

題意:

有 \(4\) 種喜好不同的人,分別最愛唱、跳、 \(rap\)、籃球,他們個數分別為 \(A,B,C,D\) ,現從他們中挑選出 \(n\) 個人並進行排列,規定不能出現喜愛唱、跳、 rap、籃球的人在序列中依次出現,問合法方案數。

下文將喜愛唱、跳、 \(rap\)、籃球的人依次出現的區間稱為聚集區間,長度為 \(4\)。

思路(容斥原理 + 生成函式 + \(\mathcal{NTT}\))

首先,我們可以發現如果順著求方案數並不好求。秉持順難逆易的原則,我們可以考慮容斥,令 \(f(k)\) 表示在長度為 \(n\) 的序列中出現了至少 \(k\) 個聚集區間的方案數,進而我們得出:\(Answer=\displaystyle\sum_{k}(-1)^{k}f(k)\)。

接下來,我們的目標就是求出 \(f(k)\)。我們可以設這 \(k\) 個聚集區間的起始位置 \(i\),並把 \(i,i+1,i+2,i+3\) 縮為一個點,這樣就一共剩下 \(n-3k\) 個點,接著,我們就可以從這 \(n-3k\) 個點中選取不在聚集區間內的點,這樣的點一共有 \(n-4k\) 個,所以方案數為 \(\binom{n-3k}{n-4k}=\binom{n-3k}{k}\)。

我們設 \(S(a,b,c,d,n)\) 表示 \(4\) 種人的數量分別有 \(a,b,c,d\) 個,從中挑選出 \(n\) 個人並進行排列的方案數。所以,\(f(k)=\binom{n-3k}{k}S(A-k,B-k,C-k,D-k,n-4k)\)

隨後,我們把注意力放在 \(S(a,b,c,d,n)\) 上,可以發現這與指數型生成函式 \(EGF\) 的應用場景非常類似。所以,我們可以寫出每種人的生成函式,例如第一種人的生成函式為 \(\displaystyle\sum_{k}^{a}\dfrac{x^k}{k!}\)。接著,我們將這四種人的生成函式進行卷積,而其中的第 \(n\) 項的係數就是答案,即 \([x^{n}]\displaystyle\sum_{k}^{a}\dfrac{x^k}{k!}\displaystyle\sum_{k}^{b}\dfrac{x^k}{k!}\displaystyle\sum_{k}^{c}\dfrac{x^k}{k!}\displaystyle\sum_{k}^{d}\dfrac{x^k}{k!}\)

綜上,\(Answer=\displaystyle\sum_{k}(-1)^{k}f(k)=\displaystyle\sum_{k}(-1)^{k}\binom{n-3k}{k}S(A-k,B-k,C-k,D-k,n-4k)\)

時間複雜度

如果卷積時使用 \(\mathcal{NTT}\),時間複雜度約為 \(n^2\log_2n\)。但由於資料不強,時限寬鬆,暴力進行卷積也可通過,時間複雜度約為 \(n^3\)。

聽說還有使用其它方法 \(AC\) 的 \(n\sqrt{n}\) 的演算法,蒟蒻不會太強了%%%

程式碼實現:

#include <map>
#include <cmath>
#include <ctime>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm> using namespace std; #define LL long long
#define Int register int
#define Lc(x) Child[x][0]
#define Rc(x) Child[x][1]
#define Swap(a, b) a ^= b ^= a ^= b
#define Abs(x) ((x) < 0 ? -(x) : (x))
#define Max(x, y) ((x) < (y) ? (y) : (x))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Isdigit(ch) (ch >= '0' and ch <= '9') const int MAXN = 1e3 + 10;
const double Pi = acos (-1.0);
const LL Mod = 998244353, G = 3, Inv2 = 499122177, INF = 1LL << 60; inline LL Read () {
LL f = 0, x = 0;
char ch = getchar (); while (!isdigit (ch) ) {
f |= (ch == '-'), ch = getchar ();
} while (isdigit (ch) ) {
x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar ();
} return f ? -x : x;
} inline void Write (const LL &x) {
if (x < 0 ) {
putchar ('-'), Write (-x);
return ;
} if (x > 9 ) {
Write (x / 10);
} putchar ((x % 10) ^ 48);
return ;
} LL Answer;
int n, a, b, c, d;
LL Fact[MAXN], Inv[MAXN], Pow[MAXN], Sum1[MAXN], Sum2[MAXN]; inline LL Qkpow (LL Base, LL x) {
LL Total = 1; while (x ) {
if (x & 1 ) {
Total = Total * Base % Mod;
} Base = Base * Base % Mod, x >>= 1;
} return Total;
} inline LL Getinv (const LL x) {
return Qkpow (x, Mod - 2);
} inline LL Getbinom (const int n, const int m) {
return Fact[n] * Inv[m] % Mod * Inv[n - m] % Mod;
} inline LL Clac (const int a, const int b, const int c, const int d, const int n) {
Int i, j; for (i = 0; i <= n; ++ i ) {
Sum1[i] = Sum2[i] = 0;
} Sum1[0] = 1; for (i = 0; i <= n; ++ i ) {
for (j = 0; j <= a and i + j <= n; ++ j ) {
Sum2[i + j] = (Sum2[i + j] + Sum1[i] * Inv[i] % Mod * Fact[i + j] % Mod * Inv[j] % Mod) % Mod;
}
} for (i = 0; i <= n; ++ i ) {
Sum1[i] = Sum2[i], Sum2[i] = 0;
// printf ("%lld ", Sum1[i]);
} // putchar ('\n'); for (i = 0; i <= n; ++ i ) {
for (j = 0; j <= b and i + j <= n; ++ j ) {
Sum2[i + j] = (Sum2[i + j] + Sum1[i] * Inv[i] % Mod * Fact[i + j] % Mod * Inv[j] % Mod) % Mod;
}
} for (i = 0; i <= n; ++ i ) {
Sum1[i] = Sum2[i], Sum2[i] = 0;
// printf ("%lld ", Sum1[i]);
} // putchar ('\n'); for (i = 0; i <= n; ++ i ) {
for (j = 0; j <= c and i + j <= n; ++ j ) {
Sum2[i + j] = (Sum2[i + j] + Sum1[i] * Inv[i] % Mod * Fact[i + j] % Mod * Inv[j] % Mod) % Mod;
}
} for (i = 0; i <= n; ++ i ) {
Sum1[i] = Sum2[i], Sum2[i] = 0;
// printf ("%lld ", Sum1[i]);
} // putchar ('\n'); for (i = 0; i <= n; ++ i ) {
for (j = 0; j <= d and i + j <= n; ++ j ) {
Sum2[i + j] = (Sum2[i + j] + Sum1[i] * Inv[i] % Mod * Fact[i + j] % Mod * Inv[j] % Mod) % Mod;
}
} for (i = 0; i <= n; ++ i ) {
Sum1[i] = Sum2[i], Sum2[i] = 0;
// printf ("%lld ", Sum1[i]);
} // putchar ('\n'); return Sum1[n];
} signed main () {
n = Read (), a = Read (), b = Read (), c = Read (), d = Read ();
Int i; for (i = Pow[0] = Fact[0] = 1; i <= n; ++ i ) {
Fact[i] = Fact[i - 1] * i % Mod, Pow[i] = Pow[i - 1] * 4 % Mod;
} Inv[n] = Getinv (Fact[n]); for (i = n - 1; ~i; -- i ) {
Inv[i] = Inv[i + 1] * (i + 1) % Mod;
} if (a == b and b == c and c == d ) {
for (i = 0; i <= n / 4; ++ i ) {
Answer = (Answer + (i & 1 ? -1 : 1) * Pow[n - 4 * i] * Getbinom (n - 3 * i, i) % Mod + Mod) % Mod;;
} Write (Answer);
return 0;
} for (i = 0; i <= n / 4 and i <= a and i <= b and i <= c and i <= d; ++ i ) {
// printf ("---------i = %d---------\n", i);
Answer = (Answer + (i & 1 ? -1 : 1) * Clac (a - i, b - i, c - i, d - i, n - 4 * i) * Getbinom (n - 3 * i, i) % Mod + Mod) % Mod;;
// Write (Answer), putchar ('\n');
} Write (Answer); return 0;
}