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.
Think in terms of pos[value], the final position of each value. For split , the riffle condition is exactly and .
So the sequence can have at most one descent. If there is a descent after value , then split is forced. If there is no descent, the permutation is just sorted.
For a fixed split , represent a shuffle by a binary string over positions: L positions receive in order, and R positions receive in order.
A fixed cell becomes a checkpoint. If , position must be an L step and after it we must have used exactly Ls. If , it must be an R step and after it we must have used exactly Rs.
Process fixed cells left-to-right. Between two checkpoints, the number of binary strings is just a binomial coefficient, but remember the current fixed position's step type is forced. Sum over all , then fix the only duplicate: the sorted permutation is counted for every split, so count it once.
Let be the final position of value .
For a split , being a riffle shuffle of means:
and
So in the value-position sequence
all adjacent pairs must increase except maybe the pair .
That gives the main characterization:
A permutation is valid iff has at most one descent.
If there is no descent, then , so the permutation is exactly sorted: .
If there is exactly one descent after value , then split is the only possible split. No double-counting nonsense.
Now count, for each split , all completions that work for that split. Then the only duplicate across different is the sorted permutation, because sorted works for every split. Everything else has one unique descent boundary.
So the answer is:
Here means: number of completions that are valid for split .
Counting one split
Fix .
Every split- riffle shuffle can be represented by a binary string of length :
L: this position gets the next value from ;R: this position gets the next value from .For example, if and the binary string is LRRLLR, the values are filled as:
Once the binary string is chosen, the actual permutation is forced.
Now look at a fixed input cell .
Suppose we scan positions left to right. After processing position , let:
L positions used so far;R positions used so far.Of course .
If , then belongs to the left block. Therefore position must be an L, and after taking that step we need:
If , then belongs to the right block. It is the -th right value, so position must be an R, and after taking that step:
So every fixed cell becomes a forced checkpoint in the binary string, plus a forced final step type for that checkpoint.
Using binomial coefficients between checkpoints
Process fixed cells in increasing position order.
Assume the previous checkpoint was after position lastPos, with counts (lastL, lastR). Now the next fixed cell is at position , and it requires counts (L, R) after position $i`.
Let:
We need exactly left steps and right steps in this segment.
But the last step of the segment, position , is forced:
L, then among the previous positions we choose left steps:R, then among the previous positions we choose left steps:If counts ever go backward, exceed or , or the forced step is impossible, then this split contributes .
After the last fixed cell, the remaining suffix has no forced endpoint. If we still need left steps in a suffix of length , it contributes:
That gives in time.
We do this for all , so the total complexity is per test file overall because the sum of is at most . Completely fine.
Why subtract sorted?
For every split , the sorted permutation corresponds to binary string:
So if the sorted permutation matches the fixed input, it appears inside every . But it is one permutation, not permutations wearing fake mustaches.
All non-sorted valid permutations have exactly one descent in , so they are counted for exactly one split.
That is the whole trick.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
ll modpow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
int main() {
setIO();
const int MAXN = 3000;
vector<ll> fact(MAXN + 1), invfact(MAXN + 1);
fact[0] = 1;
for (int i = 1; i <= MAXN; i++) fact[i] = fact[i - 1] * i % MOD;
invfact[MAXN] = modpow(fact[MAXN], MOD - 2);
for (int i = MAXN; i >= 1; i--) invfact[i - 1] = invfact[i] * i % MOD;
auto C = [&](int n, int k) -> ll {
if (k < 0 || k > n) return 0;
return fact[n] * invfact[k] % MOD * invfact[n - k] % MOD;
};
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<pair<int, int>> fixed;
bool sorted_ok = true;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x != -1) {
fixed.push_back({i, x});
if (x != i) sorted_ok = false;
}
}
ll ans = sorted_ok ? 1 : 0;
for (int k = 1; k < n; k++) {
int lastL = 0, lastR = 0, lastPos = 0;
ll ways = 1;
bool ok = true;
for (auto [i, a] : fixed) {
int L, R;
bool needL;
if (a <= k) {
needL = true;
L = a;
R = i - a;
} else {
needL = false;
R = a - k;
L = i - R;
}
int len = i - lastPos;
int dL = L - lastL;
int dR = R - lastR;
if (L < lastL || R < lastR || L > k || R > n - k || dL + dR != len) {
ok = false;
break;
}
if (needL) {
if (dL < 1) {
ok = false;
break;
}
ways = ways * C(len - 1, dL - 1) % MOD;
} else {
if (dR < 1) {
ok = false;
break;
}
ways = ways * C(len - 1, dL) % MOD;
}
lastL = L;
lastR = R;
lastPos = i;
}
if (!ok) continue;
int len = n - lastPos;
int dL = k - lastL;
int dR = (n - k) - lastR;
if (dL < 0 || dR < 0 || dL + dR != len) continue;
ways = ways * C(len, dL) % MOD;
ans = (ans + ways) % MOD;
if (sorted_ok) ans = (ans - 1 + MOD) % MOD;
}
cout << ans << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
ll modpow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
int main() {
setIO();
const int MAXN = 3000;
vector<ll> fact(MAXN + 1), invfact(MAXN + 1);
fact[0] = 1;
for (int i = 1; i <= MAXN; i++) fact[i] = fact[i - 1] * i % MOD;
invfact[MAXN] = modpow(fact[MAXN], MOD - 2);
for (int i = MAXN; i >= 1; i--) invfact[i - 1] = invfact[i] * i % MOD;
auto C = [&](int n, int k) -> ll {
if (k < 0 || k > n) return 0;
return fact[n] * invfact[k] % MOD * invfact[n - k] % MOD;
};
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<pair<int, int>> fixed;
bool sorted_ok = true;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x != -1) {
fixed.push_back({i, x});
if (x != i) sorted_ok = false;
}
}
ll ans = sorted_ok ? 1 : 0;
for (int k = 1; k < n; k++) {
int lastL = 0, lastR = 0, lastPos = 0;
ll ways = 1;
bool ok = true;
for (auto [i, a] : fixed) {
int L, R;
bool needL;
if (a <= k) {
needL = true;
L = a;
R = i - a;
} else {
needL = false;
R = a - k;
L = i - R;
}
int len = i - lastPos;
int dL = L - lastL;
int dR = R - lastR;
if (L < lastL || R < lastR || L > k || R > n - k || dL + dR != len) {
ok = false;
break;
}
if (needL) {
if (dL < 1) {
ok = false;
break;
}
ways = ways * C(len - 1, dL - 1) % MOD;
} else {
if (dR < 1) {
ok = false;
break;
}
ways = ways * C(len - 1, dL) % MOD;
}
lastL = L;
lastR = R;
lastPos = i;
}
if (!ok) continue;
int len = n - lastPos;
int dL = k - lastL;
int dR = (n - k) - lastR;
if (dL < 0 || dR < 0 || dL + dR != len) continue;
ways = ways * C(len, dL) % MOD;
ans = (ans + ways) % MOD;
if (sorted_ok) ans = (ans - 1 + MOD) % MOD;
}
cout << ans << '\n';
}
}