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.
For a fixed split , the permutation is just an interleaving of two increasing lists: and . So after choosing which positions belong to the left list, the actual values are forced.
Look at a known value at position . If it belongs to the left list, then . If it belongs to the right list, then . So forces left, forces right, and only is ambiguous.
In any non-sorted valid permutation, positions with can only form a sorted prefix and a sorted suffix. Therefore, once you see any known non-fixed value, every known fixed value before it is left-side, and every known fixed value after it is right-side.
Now process gaps of consecutive between known positions. If both endpoints are on the same side, the number of missing values from that side is fixed, so the gap contributes one binomial coefficient independent of the split.
The only split-dependent gaps are left right and right left. A crossing gap of length permits only possible split values. Intersect all those ranges, then explicitly scan only that final interval and multiply the relevant binomial coefficients.
Core View
Fix a split : the left subsequence is , and the right subsequence is . If we color every position by whether it comes from the left or right subsequence, then the values are forced: left-colored positions receive the next left value, right-colored positions receive the next right value.
So the problem is really counting valid two-colorings of the unknown positions. The annoying part is that we must count completed permutations, not split choices. A non-sorted valid permutation has a unique split. The sorted permutation works for every split, so it needs special handling.
The implementation uses 0-indexed values. So a split means:
Valid real splits are .
Forcing The Side Of Known Values
Suppose position contains known value .
If is on the left side, then by the time we reach position , the left values have appeared. That is left values inside positions, so .
If is on the right side, then all positions up to contain at most all left values plus some prefix of the right values. This gives .
Therefore:
But in a non-sorted valid permutation, fixed points are not scattered randomly. They form a sorted prefix and a sorted suffix. Once the permutation actually deviates from sorted order, the middle cannot contain another fixed point. So if at least one known value has , then while scanning left to right:
That gives the side of every known value.
Case 1: Sorted Completion Is Still Possible
This means every known value is either or exactly its own index.
The fully sorted completion always contributes .
Now consider one maximal run of unknown positions between fixed known positions or array borders. The values that can go there are exactly the same consecutive interval, so the run behaves like a smaller all-unknown problem of length .
For length , summing over split choices gives
Every non-sorted riffle shuffle is counted once, while the sorted order is counted times. So the number of distinct valid permutations is , and the number of non-sorted ones is .
Only one run can contain the unique non-sorted part; otherwise the inverse permutation would have multiple descents. So the answer in this case is
Tiny formula, big win. Very suspicious-looking, but it is exactly the sorted-overcount tax.
Case 2: Sorted Completion Is Impossible
Now every valid completion is non-sorted, hence has a unique split . We can safely sum over .
We already know the side of each known value. Process gaps of unknown positions between consecutive known positions. Also add a fake left-side sentinel before the array with value ; this makes the first known value behave like an ordinary gap.
For a gap of length between known endpoints, the number of ways to choose which unknown positions are right-side is always a binomial coefficient. The formula depends on the endpoint sides.
Let the previous known endpoint be at position with value , and the next known endpoint be at position with value . The gap length is .
For same-side gaps:
Either way, the contribution is simply
independent of . We multiply these into a global coefficient.
For crossing gaps, the contribution depends on .
For left right, the number of right positions needed inside the gap is
So this gap contributes
It is nonzero only when
For right left, the number of right positions needed inside the gap is
so the contribution is
nonzero only when
Each crossing gap of length allows only split values. Intersect all these ranges with . Let the final interval be .
Then we keep score[s] for every 1. For every crossing gap, multiply the proper binomial into score[s]. For every same-side gap, multiply its binomial into a global coef`.
Finally, handle the suffix after the last known value:
Add all score[s], multiply by coef, and that is the answer.
Why This Is Linear
Let the final candidate interval have size . Every crossing gap used to create that interval has length at least , because a crossing gap of length allows only split values. These crossing gaps are disjoint, so the total work over all crossing gaps is . Same-side gaps are processed once. Precomputed factorials give every binomial in .
Total complexity: per test case, with memory. Across all tests, .
#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 MAXN = 1000000;
vector<int> fact(MAXN + 1), invfact(MAXN + 1), pow2v(MAXN + 1);
int modpow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return (int)r;
}
void init() {
fact[0] = 1;
pow2v[0] = 1;
for (int i = 1; i <= MAXN; i++) {
fact[i] = (ll)fact[i - 1] * i % MOD;
pow2v[i] = pow2v[i - 1] * 2LL % MOD;
}
invfact[MAXN] = modpow(fact[MAXN], MOD - 2);
for (int i = MAXN; i >= 1; i--) {
invfact[i - 1] = (ll)invfact[i] * i % MOD;
}
}
int C(int n, int r) {
if (n < 0 || r < 0 || r > n) return 0;
return (ll)fact[n] * invfact[r] % MOD * invfact[n - r] % MOD;
}
int solve_case() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
if (a[i] != -1) --a[i];
}
bool sorted_possible = true;
for (int i = 0; i < n; i++) {
if (a[i] != -1 && a[i] != i) sorted_possible = false;
}
if (sorted_possible) {
ll ans = 1;
int last = -1;
for (int i = 0; i <= n; i++) {
if (i == n || a[i] != -1) {
int len = i - last - 1;
ans += pow2v[len] - len - 1;
ans %= MOD;
if (ans < 0) ans += MOD;
last = i;
}
}
return (int)ans;
}
vector<char> side(n, 0); // 0 = left, 1 = right
bool found_nonfixed = false;
for (int i = 0; i < n; i++) {
if (a[i] == -1) continue;
if (a[i] < i) side[i] = 0;
else if (a[i] > i) side[i] = 1;
else side[i] = found_nonfixed;
if (a[i] != i) found_nonfixed = true;
}
int L = 1, R = n - 1;
int last = -1;
for (int i = 0; i < n; i++) {
if (a[i] == -1) continue;
bool prev_side = (last == -1 ? 0 : side[last]);
int spaces = i - last - 1;
if (side[i] && !prev_side) {
int placed_right = (last == -1 ? 0 : last - a[last]);
L = max(L, a[i] - placed_right - spaces);
R = min(R, a[i] - placed_right);
} else if (!side[i] && prev_side) {
int lo = a[last] + a[i] + 1 - i;
L = max(L, lo);
R = min(R, lo + spaces);
}
last = i;
}
if (L > R) return 0;
vector<int> score(R - L + 1, 1);
ll coef = 1;
last = -1;
for (int i = 0; i < n; i++) {
if (a[i] == -1) continue;
bool prev_side = (last == -1 ? 0 : side[last]);
int spaces = i - last - 1;
if (side[i] == prev_side) {
int prev_value = (last == -1 ? -1 : a[last]);
coef = coef * C(spaces, a[i] - prev_value - 1) % MOD;
} else if (side[i] && !prev_side) {
int placed_right = (last == -1 ? 0 : last - a[last]);
for (int s = L; s <= R; s++) {
int need = a[i] - s - placed_right;
score[s - L] = (ll)score[s - L] * C(spaces, need) % MOD;
}
} else {
for (int s = L; s <= R; s++) {
int need = i - a[i] + s - 1 - a[last];
score[s - L] = (ll)score[s - L] * C(spaces, need) % MOD;
}
}
last = i;
}
ll ans = 0;
int tail = n - last - 1;
for (int s = L; s <= R; s++) {
ll cur = score[s - L];
if (!side[last]) cur = cur * C(tail, s - a[last] - 1) % MOD;
else cur = cur * C(tail, n - 1 - a[last]) % MOD;
ans += cur;
if (ans >= MOD) ans -= MOD;
}
return (int)(ans * coef % MOD);
}
int main() {
setIO();
init();
int T;
cin >> T;
while (T--) {
cout << solve_case() << '\n';
}
}#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 MAXN = 1000000;
vector<int> fact(MAXN + 1), invfact(MAXN + 1), pow2v(MAXN + 1);
int modpow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return (int)r;
}
void init() {
fact[0] = 1;
pow2v[0] = 1;
for (int i = 1; i <= MAXN; i++) {
fact[i] = (ll)fact[i - 1] * i % MOD;
pow2v[i] = pow2v[i - 1] * 2LL % MOD;
}
invfact[MAXN] = modpow(fact[MAXN], MOD - 2);
for (int i = MAXN; i >= 1; i--) {
invfact[i - 1] = (ll)invfact[i] * i % MOD;
}
}
int C(int n, int r) {
if (n < 0 || r < 0 || r > n) return 0;
return (ll)fact[n] * invfact[r] % MOD * invfact[n - r] % MOD;
}
int solve_case() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
if (a[i] != -1) --a[i];
}
bool sorted_possible = true;
for (int i = 0; i < n; i++) {
if (a[i] != -1 && a[i] != i) sorted_possible = false;
}
if (sorted_possible) {
ll ans = 1;
int last = -1;
for (int i = 0; i <= n; i++) {
if (i == n || a[i] != -1) {
int len = i - last - 1;
ans += pow2v[len] - len - 1;
ans %= MOD;
if (ans < 0) ans += MOD;
last = i;
}
}
return (int)ans;
}
vector<char> side(n, 0); // 0 = left, 1 = right
bool found_nonfixed = false;
for (int i = 0; i < n; i++) {
if (a[i] == -1) continue;
if (a[i] < i) side[i] = 0;
else if (a[i] > i) side[i] = 1;
else side[i] = found_nonfixed;
if (a[i] != i) found_nonfixed = true;
}
int L = 1, R = n - 1;
int last = -1;
for (int i = 0; i < n; i++) {
if (a[i] == -1) continue;
bool prev_side = (last == -1 ? 0 : side[last]);
int spaces = i - last - 1;
if (side[i] && !prev_side) {
int placed_right = (last == -1 ? 0 : last - a[last]);
L = max(L, a[i] - placed_right - spaces);
R = min(R, a[i] - placed_right);
} else if (!side[i] && prev_side) {
int lo = a[last] + a[i] + 1 - i;
L = max(L, lo);
R = min(R, lo + spaces);
}
last = i;
}
if (L > R) return 0;
vector<int> score(R - L + 1, 1);
ll coef = 1;
last = -1;
for (int i = 0; i < n; i++) {
if (a[i] == -1) continue;
bool prev_side = (last == -1 ? 0 : side[last]);
int spaces = i - last - 1;
if (side[i] == prev_side) {
int prev_value = (last == -1 ? -1 : a[last]);
coef = coef * C(spaces, a[i] - prev_value - 1) % MOD;
} else if (side[i] && !prev_side) {
int placed_right = (last == -1 ? 0 : last - a[last]);
for (int s = L; s <= R; s++) {
int need = a[i] - s - placed_right;
score[s - L] = (ll)score[s - L] * C(spaces, need) % MOD;
}
} else {
for (int s = L; s <= R; s++) {
int need = i - a[i] + s - 1 - a[last];
score[s - L] = (ll)score[s - L] * C(spaces, need) % MOD;
}
}
last = i;
}
ll ans = 0;
int tail = n - last - 1;
for (int s = L; s <= R; s++) {
ll cur = score[s - L];
if (!side[last]) cur = cur * C(tail, s - a[last] - 1) % MOD;
else cur = cur * C(tail, n - 1 - a[last]) % MOD;
ans += cur;
if (ans >= MOD) ans -= MOD;
}
return (int)(ans * coef % MOD);
}
int main() {
setIO();
init();
int T;
cin >> T;
while (T--) {
cout << solve_case() << '\n';
}
}