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.
The permutation is an order of splitting intervals. Once a cell becomes black, it cuts the remaining white cells around it into independent gaps.
A gap is not always bounded on both sides. Left of the first black cell there is only a right boundary, and right of it there is only a left boundary. Fake boundaries do not receive points. That assumption is the sample-killer.
For a two-sided gap between black cells and , if the first chosen inside cell is , then gives one point to iff ; otherwise it gives one point to . Ties go left.
Store interval DP states by how many points the outside boundary cells receive. When is chosen first, its final score is the sum of the points it receives from the two child gaps.
After picking the first global cell , combine a right-bounded DP for cells left of and a left-bounded DP for cells right of . Multiply by the binomial number of ways to interleave the two side orders.
We need count orders of coloring cells. The clean way to see the process is: every chosen cell splits one current gap into smaller gaps.
The dangerous false assumption is using fake cells outside the array as real black boundaries. They are not real. If the first black cell is , then every cell left of initially has only one black boundary, namely . Same for the right side. A fake boundary stealing points is just wrong.
Interval Types
We use three kinds of interval DP.
Let describe a two-sided gap: cells strictly between already-black real cells and . A state means this whole gap contributes points to and points to .
Let describe a right-bounded gap: cells are uncolored, and only is already black. A state means boundary receives points.
Let describe a left-bounded gap: cells are uncolored, and only is already black. A state means boundary receives points.
Empty gaps have exactly one way with contribution .
Two-Sided Transition
For , choose the first cell with .
At that exact moment, the nearest black cell to is either or . The point goes left if
,
otherwise it goes right.
Then the two child gaps are independent: and .
Suppose the left child gives , meaning points to and points to . Suppose the right child gives , meaning points to and points to .
Then the final score of cell is . If is fixed, we require .
The outer boundary contributions become:
If the left child has cells and the right child has cells, their internal orders can be interleaved in ways.
One-Sided Transitions
For , the first chosen cell lies in . Since is the only existing black cell, gives one point to .
After choosing :
If those children give points to from the left side and points to from the right side, then ends with score .
The left-bounded DP is symmetric: choose in , gives one point to , then combine and .
Choosing The First Cell
Let the first colored cell be . It receives no point immediately.
The left side is . The right side is .
If these contribute and points to , then ends with score , which must match if fixed.
The two side orders can be interleaved in ways. Sum over all .
Why Sparse States Are Enough
A boundary cannot receive tons of points from the same side. In a two-sided gap, if a point goes to the left boundary, the remaining cells that can still later score that same boundary lie in at most the left half of the gap. So each extra point roughly halves the relevant interval. Same idea on the right, and one-sided gaps only add one initial point before becoming two-sided near that boundary.
So the number of nonzero contribution values per boundary is only about . The implementation stores only nonzero states. This keeps the DP small enough for without any drama.
#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;
struct State2 {
int a, b, ways;
};
struct State1 {
int a, ways;
};
int addmod(int a, int b) {
a += b;
if (a >= MOD) a -= MOD;
return a;
}
int mulmod(ll a, ll b) {
return int(a * b % MOD);
}
int main() {
setIO();
const int MAXN = 105;
vector<vector<int>> comb(MAXN, vector<int>(MAXN));
for (int i = 0; i < MAXN; i++) {
comb[i][0] = comb[i][i] = 1;
for (int j = 1; j < i; j++) {
comb[i][j] = addmod(comb[i - 1][j - 1], comb[i - 1][j]);
}
}
int tc;
cin >> tc;
while (tc--) {
int n;
cin >> n;
vector<int> s(n + 1);
for (int i = 1; i <= n; i++) cin >> s[i];
vector<vector<vector<State2>>> both(n + 2, vector<vector<State2>>(n + 2));
vector<vector<vector<State1>>> onlyR(n + 2, vector<vector<State1>>(n + 2));
vector<vector<vector<State1>>> onlyL(n + 2, vector<vector<State1>>(n + 2));
int acc2[MAXN][MAXN] = {};
int seen2[MAXN][MAXN] = {};
int acc1[MAXN] = {};
int seen1[MAXN] = {};
int stamp2 = 0, stamp1 = 0;
auto add2 = [&](vector<pair<int, int>>& touched, int a, int b, int val) {
if (seen2[a][b] != stamp2) {
seen2[a][b] = stamp2;
acc2[a][b] = 0;
touched.push_back({a, b});
}
acc2[a][b] = addmod(acc2[a][b], val);
};
auto add1 = [&](vector<int>& touched, int a, int val) {
if (seen1[a] != stamp1) {
seen1[a] = stamp1;
acc1[a] = 0;
touched.push_back(a);
}
acc1[a] = addmod(acc1[a], val);
};
for (int len = 1; len <= n - 1; len++) {
for (int L = 1; L + len <= n; L++) {
int R = L + len;
if (len == 1) {
both[L][R].push_back({0, 0, 1});
continue;
}
++stamp2;
vector<pair<int, int>> touched;
for (int x = L + 1; x <= R - 1; x++) {
int leftCnt = x - L - 1;
int rightCnt = R - x - 1;
int mergeWays = comb[leftCnt + rightCnt][leftCnt];
int addL = (x - L <= R - x);
int addR = 1 - addL;
for (const auto& A : both[L][x]) {
for (const auto& B : both[x][R]) {
if (s[x] != -1 && A.b + B.a != s[x]) continue;
int ways = mulmod(mulmod(A.ways, B.ways), mergeWays);
add2(touched, A.a + addL, B.b + addR, ways);
}
}
}
for (auto [a, b] : touched) {
if (acc2[a][b]) both[L][R].push_back({a, b, acc2[a][b]});
}
}
}
for (int i = 1; i <= n; i++) {
onlyR[i][i].push_back({0, 1});
onlyL[i][i].push_back({0, 1});
}
for (int len = 1; len <= n - 1; len++) {
for (int R = 1; R <= n; R++) {
int L = R - len;
if (L < 1) continue;
++stamp1;
vector<int> touched;
for (int x = L; x <= R - 1; x++) {
int leftCnt = x - L;
int rightCnt = R - x - 1;
int mergeWays = comb[leftCnt + rightCnt][leftCnt];
for (const auto& A : onlyR[L][x]) {
for (const auto& B : both[x][R]) {
if (s[x] != -1 && A.a + B.a != s[x]) continue;
int ways = mulmod(mulmod(A.ways, B.ways), mergeWays);
add1(touched, B.b + 1, ways);
}
}
}
for (int a : touched) {
if (acc1[a]) onlyR[L][R].push_back({a, acc1[a]});
}
}
for (int L = 1; L <= n; L++) {
int R = L + len;
if (R > n) continue;
++stamp1;
vector<int> touched;
for (int x = L + 1; x <= R; x++) {
int leftCnt = x - L - 1;
int rightCnt = R - x;
int mergeWays = comb[leftCnt + rightCnt][leftCnt];
for (const auto& A : both[L][x]) {
for (const auto& B : onlyL[x][R]) {
if (s[x] != -1 && A.b + B.a != s[x]) continue;
int ways = mulmod(mulmod(A.ways, B.ways), mergeWays);
add1(touched, A.a + 1, ways);
}
}
}
for (int a : touched) {
if (acc1[a]) onlyL[L][R].push_back({a, acc1[a]});
}
}
}
int ans = 0;
for (int r = 1; r <= n; r++) {
int mergeWays = comb[n - 1][r - 1];
for (const auto& L : onlyR[1][r]) {
for (const auto& R : onlyL[r][n]) {
if (s[r] != -1 && L.a + R.a != s[r]) continue;
int ways = mulmod(mulmod(L.ways, R.ways), mergeWays);
ans = addmod(ans, ways);
}
}
}
cout << ans << '\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;
struct State2 {
int a, b, ways;
};
struct State1 {
int a, ways;
};
int addmod(int a, int b) {
a += b;
if (a >= MOD) a -= MOD;
return a;
}
int mulmod(ll a, ll b) {
return int(a * b % MOD);
}
int main() {
setIO();
const int MAXN = 105;
vector<vector<int>> comb(MAXN, vector<int>(MAXN));
for (int i = 0; i < MAXN; i++) {
comb[i][0] = comb[i][i] = 1;
for (int j = 1; j < i; j++) {
comb[i][j] = addmod(comb[i - 1][j - 1], comb[i - 1][j]);
}
}
int tc;
cin >> tc;
while (tc--) {
int n;
cin >> n;
vector<int> s(n + 1);
for (int i = 1; i <= n; i++) cin >> s[i];
vector<vector<vector<State2>>> both(n + 2, vector<vector<State2>>(n + 2));
vector<vector<vector<State1>>> onlyR(n + 2, vector<vector<State1>>(n + 2));
vector<vector<vector<State1>>> onlyL(n + 2, vector<vector<State1>>(n + 2));
int acc2[MAXN][MAXN] = {};
int seen2[MAXN][MAXN] = {};
int acc1[MAXN] = {};
int seen1[MAXN] = {};
int stamp2 = 0, stamp1 = 0;
auto add2 = [&](vector<pair<int, int>>& touched, int a, int b, int val) {
if (seen2[a][b] != stamp2) {
seen2[a][b] = stamp2;
acc2[a][b] = 0;
touched.push_back({a, b});
}
acc2[a][b] = addmod(acc2[a][b], val);
};
auto add1 = [&](vector<int>& touched, int a, int val) {
if (seen1[a] != stamp1) {
seen1[a] = stamp1;
acc1[a] = 0;
touched.push_back(a);
}
acc1[a] = addmod(acc1[a], val);
};
for (int len = 1; len <= n - 1; len++) {
for (int L = 1; L + len <= n; L++) {
int R = L + len;
if (len == 1) {
both[L][R].push_back({0, 0, 1});
continue;
}
++stamp2;
vector<pair<int, int>> touched;
for (int x = L + 1; x <= R - 1; x++) {
int leftCnt = x - L - 1;
int rightCnt = R - x - 1;
int mergeWays = comb[leftCnt + rightCnt][leftCnt];
int addL = (x - L <= R - x);
int addR = 1 - addL;
for (const auto& A : both[L][x]) {
for (const auto& B : both[x][R]) {
if (s[x] != -1 && A.b + B.a != s[x]) continue;
int ways = mulmod(mulmod(A.ways, B.ways), mergeWays);
add2(touched, A.a + addL, B.b + addR, ways);
}
}
}
for (auto [a, b] : touched) {
if (acc2[a][b]) both[L][R].push_back({a, b, acc2[a][b]});
}
}
}
for (int i = 1; i <= n; i++) {
onlyR[i][i].push_back({0, 1});
onlyL[i][i].push_back({0, 1});
}
for (int len = 1; len <= n - 1; len++) {
for (int R = 1; R <= n; R++) {
int L = R - len;
if (L < 1) continue;
++stamp1;
vector<int> touched;
for (int x = L; x <= R - 1; x++) {
int leftCnt = x - L;
int rightCnt = R - x - 1;
int mergeWays = comb[leftCnt + rightCnt][leftCnt];
for (const auto& A : onlyR[L][x]) {
for (const auto& B : both[x][R]) {
if (s[x] != -1 && A.a + B.a != s[x]) continue;
int ways = mulmod(mulmod(A.ways, B.ways), mergeWays);
add1(touched, B.b + 1, ways);
}
}
}
for (int a : touched) {
if (acc1[a]) onlyR[L][R].push_back({a, acc1[a]});
}
}
for (int L = 1; L <= n; L++) {
int R = L + len;
if (R > n) continue;
++stamp1;
vector<int> touched;
for (int x = L + 1; x <= R; x++) {
int leftCnt = x - L - 1;
int rightCnt = R - x;
int mergeWays = comb[leftCnt + rightCnt][leftCnt];
for (const auto& A : both[L][x]) {
for (const auto& B : onlyL[x][R]) {
if (s[x] != -1 && A.b + B.a != s[x]) continue;
int ways = mulmod(mulmod(A.ways, B.ways), mergeWays);
add1(touched, A.a + 1, ways);
}
}
}
for (int a : touched) {
if (acc1[a]) onlyL[L][R].push_back({a, acc1[a]});
}
}
}
int ans = 0;
for (int r = 1; r <= n; r++) {
int mergeWays = comb[n - 1][r - 1];
for (const auto& L : onlyR[1][r]) {
for (const auto& R : onlyL[r][n]) {
if (s[r] != -1 && L.a + R.a != s[r]) continue;
int ways = mulmod(mulmod(L.ways, R.ways), mergeWays);
ans = addmod(ans, ways);
}
}
}
cout << ans << '\n';
}
}