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.
Start with the last position. If , then and every . That smells like a split, not like random local constraints.
That split is real. The prefix is independent, and the block becomes another cute array after subtracting from its parent labels, where parent turns into local .
So cute arrays are Catalan objects. Equivalently, keep a stack initially ; for position , choose from the stack, pop everything above it, then push .
A fixed value says that must still be on the stack at time . If two fixed choices have and , then gets popped at time and can never come back. That crossing means answer .
Process fixed edges by increasing length . Before forcing , count the free segment with a depth DP: a forced compressed edge does , while a free vertex does . Suffix sums make each segment fast; total work is .
Look at the last element of a cute array . Let .
If , then by definition
So every index in the right block has as a smaller element before it. Its parent label is therefore at least . After subtracting , this right block becomes a smaller cute array, where global parent becomes local .
The prefix is also independent. Thus the key recurrence is:
The converse works by shifting values in the right block upward and choosing between and all right-block values. For , choose below everything before it. No wizardry, just inequalities doing their job.
This is exactly the Catalan recurrence. More usefully, it gives an online stack process:
A sequence is cute if and only if it can be produced by this process. Think of it as preorder labels of a rooted ordered tree. Catalan objects wearing a fake mustache.
If , then at time we are forced to choose stack element .
First, must hold. Otherwise is trying to use itself or the future as a parent, which is nonsense.
Second, two fixed choices cannot cross. For fixed , if
then at time choosing pops every active vertex above . The already-created vertex lies above , so after time it is gone from the stack forever. Then time can never choose . Dead.
So any such pair makes the answer .
Write a fixed choice as a forced edge . Process all forced edges in increasing order of length .
Maintain jump[u]. If jump[u]=v, it means the already-processed part from up to has been compressed into one forced push for future, larger segments.
For a fixed edge :
calc(u, v-1).jump[u]=v.If jump[u] already existed, overwriting is intentional: that older compressed block has just been included inside the new larger block.
After all fixed edges are processed, call calc(0,n) using the artificial root .
The no-crossing check guarantees that when we process , every fixed edge that can affect the inside of this segment has smaller length and was already compressed. That is the whole trick. Without it, this would be cubic garbage.
calc(l,r)During calc(l,r), we scan forward from the current vertex cur. The segment start is already on the stack.
Let be the number of ways after scanning to cur, where cur has relative depth from the segment start. Depth means there are available stack vertices inside this segment.
Initially:
There are two transitions.
If jump[cur] exists, the next vertex is forced to be a child of cur. So the depth simply increases:
Otherwise, the next raw vertex is free. From old depth , it may attach to any ancestor at depth . If it attaches to depth , the new depth is . Therefore
This transition is just suffix sums. At the end of the segment, any depth is acceptable, so multiply the global answer by
The crossing check costs . The compressed segment DP also costs total over one test case: each compressed layer is expanded only when it is created, and later it is followed as one forced jump. Memory is .
With , this is comfortably within limits.
Reference checked for verification: official Codeforces editorial.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
const int MOD = 998244353;
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> x(n + 1);
for (int i = 1; i <= n; ++i) cin >> x[i];
bool ok = true;
for (int i = 1; i <= n; ++i) {
if (x[i] != -1 && x[i] >= i) ok = false;
}
for (int i = 1; i <= n; ++i) {
if (x[i] == -1) continue;
for (int j = i + 1; j <= n; ++j) {
if (x[j] == -1) continue;
if (x[i] < x[j] && x[j] < i) ok = false;
}
}
if (!ok) {
cout << 0 << '\n';
continue;
}
vector<int> fixed_pos;
for (int i = 1; i <= n; ++i) {
if (x[i] != -1) fixed_pos.push_back(i);
}
sort(fixed_pos.begin(), fixed_pos.end(), [&](int a, int b) {
return a - x[a] < b - x[b];
});
vector<int> jump(n + 2, 0);
ll ans = 1;
auto calc = [&](int l, int r) {
vector<int> dp(n + 2, 0), suf(n + 3, 0);
dp[0] = suf[0] = 1;
int depth = 0;
while (true) {
bool forced = false;
if (jump[l] == 0) {
++l;
} else {
l = jump[l];
forced = true;
}
if (l > r) break;
++depth;
if (forced) {
for (int d = depth; d >= 1; --d) dp[d] = dp[d - 1];
dp[0] = 0;
} else {
for (int d = 1; d <= depth; ++d) dp[d] = suf[d - 1];
dp[0] = 0;
}
suf[depth + 1] = 0;
for (int d = depth; d >= 0; --d) {
int val = suf[d + 1] + dp[d];
if (val >= MOD) val -= MOD;
suf[d] = val;
}
}
ans = ans * suf[0] % MOD;
};
for (int v : fixed_pos) {
calc(x[v], v - 1);
jump[x[v]] = v;
}
calc(0, n);
cout << ans % 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);
}
int main() {
setIO();
const int MOD = 998244353;
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> x(n + 1);
for (int i = 1; i <= n; ++i) cin >> x[i];
bool ok = true;
for (int i = 1; i <= n; ++i) {
if (x[i] != -1 && x[i] >= i) ok = false;
}
for (int i = 1; i <= n; ++i) {
if (x[i] == -1) continue;
for (int j = i + 1; j <= n; ++j) {
if (x[j] == -1) continue;
if (x[i] < x[j] && x[j] < i) ok = false;
}
}
if (!ok) {
cout << 0 << '\n';
continue;
}
vector<int> fixed_pos;
for (int i = 1; i <= n; ++i) {
if (x[i] != -1) fixed_pos.push_back(i);
}
sort(fixed_pos.begin(), fixed_pos.end(), [&](int a, int b) {
return a - x[a] < b - x[b];
});
vector<int> jump(n + 2, 0);
ll ans = 1;
auto calc = [&](int l, int r) {
vector<int> dp(n + 2, 0), suf(n + 3, 0);
dp[0] = suf[0] = 1;
int depth = 0;
while (true) {
bool forced = false;
if (jump[l] == 0) {
++l;
} else {
l = jump[l];
forced = true;
}
if (l > r) break;
++depth;
if (forced) {
for (int d = depth; d >= 1; --d) dp[d] = dp[d - 1];
dp[0] = 0;
} else {
for (int d = 1; d <= depth; ++d) dp[d] = suf[d - 1];
dp[0] = 0;
}
suf[depth + 1] = 0;
for (int d = depth; d >= 0; --d) {
int val = suf[d + 1] + dp[d];
if (val >= MOD) val -= MOD;
suf[d] = val;
}
}
ans = ans * suf[0] % MOD;
};
for (int v : fixed_pos) {
calc(x[v], v - 1);
jump[x[v]] = v;
}
calc(0, n);
cout << ans % MOD << '\n';
}
return 0;
}