Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Do not try to list or DP over all beautiful arrays. Instead, stare at the first array : in any perfect word, there must be some first position where all values of have appeared.
Let be that first completion position for , and let . Since did not appear before , every beautiful array starting with forces its remaining elements to appear after position .
So the suffix after the first completion of must itself be perfect for . This gives a recursive, unique decomposition into segments: segment ends when it first contains every value of .
For one segment with required values, choose the order in which those values first appear. There are orders. While required values are still unseen, filler characters have exactly choices, because they must avoid those unseen values.
Let , , , and for . The answer is . Expand each factor with negative binomial coefficients and multiply the six polynomials with NTT.
Let be the set from row . The scary phrase is every beautiful array. Do not build a state for all products; that road explodes instantly.
The Split Lemma Look at a candidate word and the first position where the prefix contains every value of . If such a position does not exist, obviously is not perfect.
Let . Because is the first completion time, and did not appear earlier. Suppose is perfect. Take any valid tail with . The sequence must be a subsequence of . Its first element cannot be matched before , so the whole tail must appear after . Therefore the suffix after is perfect for .
The reverse direction is easy: if a prefix up to contains all of and the suffix after is perfect for the rest, choose inside the prefix and match the rest in the suffix.
So every perfect word has a unique recursive decomposition: finish for the first time, then finish for the first time in the remaining suffix, and so on. This is the whole trick. The actual values are bait; only the lengths matter.
Counting One Segment Consider one array of length . In its segment, the required values make their first appearances in some order. There are choices for that order.
Before the first new required value appears, none of the required values may appear, so each filler character has choices. After one required value has appeared, filler may use irrelevant values and that seen value, but not the remaining unseen required values, so there are choices. In general, while required values are still unseen, filler characters have choices. Then the next character is the first appearance of one of those unseen values.
After all arrays are finished, the remaining suffix has no restrictions, so it contributes one more filler zone with choices per character.
The canonical decomposition prevents double counting. For a fixed word, the completion positions and first-appearance orders are forced.
Generating Function Let
The required first-appearance characters are already fixed by the segment orders. We only need to distribute extra filler characters among the filler zones.
For , a segment with length at least has exactly one filler zone where required values are still unseen. Define
A filler zone with choices per character contributes
Therefore the answer is
Notice why overlaps between different arrays do not change anything. When we are inside segment , the only question is whether a value belongs to the unseen part of . Future arrays are not active yet. Same integer, different segment, fresh obligation. Annoying, yes, but also clean as hell.
Computing The Coefficient Use the negative binomial expansion:
Build the six polynomials up to degree , multiply them modulo , and take coefficient . Since and there are only six factors, NTT is the right hammer here.
Factorials up to are enough for all binomial coefficients. Finally multiply by .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 998244353;
const int ROOT = 3;
int modPow(ll a, ll e) {
ll r = 1;
while (e > 0) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return int(r);
}
void ntt(vector<int>& a, bool invert) {
int n = (int)a.size();
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j ^= bit;
if (i < j) swap(a[i], a[j]);
}
for (int len = 2; len <= n; len <<= 1) {
int wlen = modPow(ROOT, (MOD - 1) / len);
if (invert) wlen = modPow(wlen, MOD - 2);
for (int i = 0; i < n; i += len) {
ll w = 1;
int half = len >> 1;
for (int j = 0; j < half; j++) {
int u = a[i + j];
int v = int(a[i + j + half] * w % MOD);
int x = u + v;
if (x >= MOD) x -= MOD;
int y = u - v;
if (y < 0) y += MOD;
a[i + j] = x;
a[i + j + half] = y;
w = w * wlen % MOD;
}
}
}
if (invert) {
int invN = modPow(n, MOD - 2);
for (int& x : a) x = int((ll)x * invN % MOD);
}
}
vector<int> multiply(vector<int> a, vector<int> b, int limit) {
int full = (int)a.size() + (int)b.size() - 1;
int need = min(limit, full);
int n = 1;
while (n < full) n <<= 1;
a.resize(n);
b.resize(n);
ntt(a, false);
ntt(b, false);
for (int i = 0; i < n; i++) a[i] = int((ll)a[i] * b[i] % MOD);
ntt(a, true);
a.resize(need);
return a;
}
vector<int> fact, invFact;
int C(int n, int r) {
if (r < 0 || r > n) return 0;
return int((ll)fact[n] * invFact[r] % MOD * invFact[n - r] % MOD);
}
int main() {
setIO();
int n, m, k;
cin >> n >> m >> k;
vector<int> len(k);
int used = 0;
vector<int> segments(6, 0);
segments[0] = 1;
for (int i = 0; i < k; i++) {
cin >> len[i];
used += len[i];
for (int q = 1; q <= len[i]; q++) segments[q]++;
}
for (int i = 0; i < k; i++) {
for (int j = 0; j < len[i]; j++) {
int x;
cin >> x;
}
}
fact.assign(n + 1, 1);
invFact.assign(n + 1, 1);
for (int i = 1; i <= n; i++) fact[i] = int((ll)fact[i - 1] * i % MOD);
invFact[n] = modPow(fact[n], MOD - 2);
for (int i = n; i >= 1; i--) invFact[i - 1] = int((ll)invFact[i] * i % MOD);
int extra = n - used;
vector<int> ans(1, 1);
for (int q = 0; q <= 5; q++) {
int s = segments[q];
if (s == 0) continue;
int base = m - q;
vector<int> poly(extra + 1);
int power = 1;
for (int t = 0; t <= extra; t++) {
poly[t] = int((ll)power * C(t + s - 1, t) % MOD);
power = int((ll)power * base % MOD);
}
ans = multiply(ans, poly, extra + 1);
}
int coef = 1;
for (int x : len) coef = int((ll)coef * fact[x] % MOD);
cout << int((ll)coef * ans[extra] % MOD) << '\n';
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 998244353;
const int ROOT = 3;
int modPow(ll a, ll e) {
ll r = 1;
while (e > 0) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return int(r);
}
void ntt(vector<int>& a, bool invert) {
int n = (int)a.size();
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j ^= bit;
if (i < j) swap(a[i], a[j]);
}
for (int len = 2; len <= n; len <<= 1) {
int wlen = modPow(ROOT, (MOD - 1) / len);
if (invert) wlen = modPow(wlen, MOD - 2);
for (int i = 0; i < n; i += len) {
ll w = 1;
int half = len >> 1;
for (int j = 0; j < half; j++) {
int u = a[i + j];
int v = int(a[i + j + half] * w % MOD);
int x = u + v;
if (x >= MOD) x -= MOD;
int y = u - v;
if (y < 0) y += MOD;
a[i + j] = x;
a[i + j + half] = y;
w = w * wlen % MOD;
}
}
}
if (invert) {
int invN = modPow(n, MOD - 2);
for (int& x : a) x = int((ll)x * invN % MOD);
}
}
vector<int> multiply(vector<int> a, vector<int> b, int limit) {
int full = (int)a.size() + (int)b.size() - 1;
int need = min(limit, full);
int n = 1;
while (n < full) n <<= 1;
a.resize(n);
b.resize(n);
ntt(a, false);
ntt(b, false);
for (int i = 0; i < n; i++) a[i] = int((ll)a[i] * b[i] % MOD);
ntt(a, true);
a.resize(need);
return a;
}
vector<int> fact, invFact;
int C(int n, int r) {
if (r < 0 || r > n) return 0;
return int((ll)fact[n] * invFact[r] % MOD * invFact[n - r] % MOD);
}
int main() {
setIO();
int n, m, k;
cin >> n >> m >> k;
vector<int> len(k);
int used = 0;
vector<int> segments(6, 0);
segments[0] = 1;
for (int i = 0; i < k; i++) {
cin >> len[i];
used += len[i];
for (int q = 1; q <= len[i]; q++) segments[q]++;
}
for (int i = 0; i < k; i++) {
for (int j = 0; j < len[i]; j++) {
int x;
cin >> x;
}
}
fact.assign(n + 1, 1);
invFact.assign(n + 1, 1);
for (int i = 1; i <= n; i++) fact[i] = int((ll)fact[i - 1] * i % MOD);
invFact[n] = modPow(fact[n], MOD - 2);
for (int i = n; i >= 1; i--) invFact[i - 1] = int((ll)invFact[i] * i % MOD);
int extra = n - used;
vector<int> ans(1, 1);
for (int q = 0; q <= 5; q++) {
int s = segments[q];
if (s == 0) continue;
int base = m - q;
vector<int> poly(extra + 1);
int power = 1;
for (int t = 0; t <= extra; t++) {
poly[t] = int((ll)power * C(t + s - 1, t) % MOD);
power = int((ll)power * base % MOD);
}
ans = multiply(ans, poly, extra + 1);
}
int coef = 1;
for (int x : len) coef = int((ll)coef * fact[x] % MOD);
cout << int((ll)coef * ans[extra] % MOD) << '\n';
return 0;
}