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.
Forget the actual numbers in the permutation for a second. Only their relative order matters. A permutation is just a total ordering of the vertices by increasing .
On one root-to-node chain, the values tell you exactly where each new vertex is inserted into the already ordered list of its ancestors. So while DFSing the tree, maintain the ancestors sorted by value.
For a vertex , let be the ancestor immediately before it and the ancestor immediately after it in that ordered ancestor list, using two sentinels for and . Then is directly placed in the interval .
Group vertices by their direct interval . Inside one interval, every direct vertex creates one component: everything in , then , then everything in .
Different direct vertices in the same interval are incomparable, so their components are just shuffled together. If their sizes are , multiply by the multinomial coefficient. That is the whole DP; the scary 2600 mask comes off pretty fast.
Turn ranks into intervals
The first trap is thinking about the actual values . Do not. The only thing that matters is the relative order of vertices by increasing .
For a vertex , says: among the ancestors of , exactly are before in this order. So if the ancestors of are already sorted by their values, then must be inserted at position in that sorted list.
During a DFS of the original rooted tree, keep a vector chain: the current root-to-parent path, sorted by value order forced by the ranks so far. When visiting :
chain[a[u]-1], or a left sentinel if ;chain[a[u]], or a right sentinel if is the current chain length.Call them and . Then is directly inserted into the interval , meaning
in the final value order.
After recording that interval, insert into chain at position , DFS its children, then erase it. Since , vector insertion/erasure is totally fine: worst case.
Why immediate intervals are enough
Suppose the ancestor list around looks like
after inserting . All ancestors before are already less than , hence less than . All ancestors after are already greater than , hence greater than . So forcing only the two neighboring inequalities also forces the correct comparison against every ancestor. Nice, no need to dump all ancestor edges like a brute-force goblin. Actually wait, no goblins. Like a brute-force spreadsheet from hell.
So the problem becomes: count the total orders satisfying all these interval constraints.
The interval DP
Let be all vertices directly inserted into interval .
Define solve(L,R) to return:
size: how many real vertices lie inside interval ;ways: how many valid orders those vertices have, assuming and are fixed boundaries.Now look at one direct vertex .
Everything in interval must be before . Everything in interval must be after . Therefore forms one component:
Its size is
and its internal number of ways is
Different direct vertices inside the same interval are incomparable. If two such vertices were ancestor and descendant in the original tree, then the ancestor would sit between and and the descendant could not have the same direct interval. So these components are independent chunks that can be interleaved arbitrarily.
If we already placed components of total size , and the next component has size , we choose its positions in
ways. Multiply that by the component's internal ways.
So the transition is:
ways = 1, total = 0
for x in B(L,R):
left = solve(L,x)
right = solve(x,R)
s = left.size + 1 + right.size
w = left.ways * right.ways
ways *= w * C(total+s, s)
total += sThe answer is solve(0,n+1).ways, where and are the two sentinels.
Complexity
Building intervals with the DFS costs because of vector insert/erase on root-to-node chains. The interval DP itself touches each vertex as a direct interval member once, so it is plus hash-map overhead. Precompute factorials and inverse factorials for combinations.
Total complexity per all tests is in the worst chain case, which is fine for .
#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;
ll mod_pow(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 = 5000;
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] = mod_pow(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 tc;
cin >> tc;
while (tc--) {
int n;
cin >> n;
vector<vector<int>> children(n + 1);
for (int i = 2; i <= n; i++) {
int p;
cin >> p;
children[p].push_back(i);
}
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
int base = n + 2;
auto key = [&](int l, int r) -> ll {
return 1LL * l * base + r;
};
unordered_map<ll, vector<int>> bucket;
bucket.reserve(2 * n + 10);
bucket.max_load_factor(0.7);
vector<int> chain;
function<void(int)> dfs = [&](int u) {
int pos = a[u];
int l = (pos == 0 ? 0 : chain[pos - 1]);
int r = (pos == (int)chain.size() ? n + 1 : chain[pos]);
bucket[key(l, r)].push_back(u);
chain.insert(chain.begin() + pos, u);
for (int v : children[u]) dfs(v);
chain.erase(chain.begin() + pos);
};
dfs(1);
unordered_map<ll, pair<int, ll>> memo;
memo.reserve(2 * n + 10);
memo.max_load_factor(0.7);
function<pair<int, ll>(int, int)> solve = [&](int l, int r) -> pair<int, ll> {
ll k = key(l, r);
if (memo.count(k)) return memo[k];
int total = 0;
ll ways = 1;
auto it = bucket.find(k);
if (it != bucket.end()) {
for (int x : it->second) {
auto [ls, lw] = solve(l, x);
auto [rs, rw] = solve(x, r);
int sz = ls + 1 + rs;
ll inside = lw * rw % MOD;
ways = ways * inside % MOD * C(total + sz, sz) % MOD;
total += sz;
}
}
return memo[k] = {total, ways};
};
cout << solve(0, n + 1).second << '\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;
ll mod_pow(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 = 5000;
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] = mod_pow(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 tc;
cin >> tc;
while (tc--) {
int n;
cin >> n;
vector<vector<int>> children(n + 1);
for (int i = 2; i <= n; i++) {
int p;
cin >> p;
children[p].push_back(i);
}
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
int base = n + 2;
auto key = [&](int l, int r) -> ll {
return 1LL * l * base + r;
};
unordered_map<ll, vector<int>> bucket;
bucket.reserve(2 * n + 10);
bucket.max_load_factor(0.7);
vector<int> chain;
function<void(int)> dfs = [&](int u) {
int pos = a[u];
int l = (pos == 0 ? 0 : chain[pos - 1]);
int r = (pos == (int)chain.size() ? n + 1 : chain[pos]);
bucket[key(l, r)].push_back(u);
chain.insert(chain.begin() + pos, u);
for (int v : children[u]) dfs(v);
chain.erase(chain.begin() + pos);
};
dfs(1);
unordered_map<ll, pair<int, ll>> memo;
memo.reserve(2 * n + 10);
memo.max_load_factor(0.7);
function<pair<int, ll>(int, int)> solve = [&](int l, int r) -> pair<int, ll> {
ll k = key(l, r);
if (memo.count(k)) return memo[k];
int total = 0;
ll ways = 1;
auto it = bucket.find(k);
if (it != bucket.end()) {
for (int x : it->second) {
auto [ls, lw] = solve(l, x);
auto [rs, rw] = solve(x, r);
int sz = ls + 1 + rs;
ll inside = lw * rw % MOD;
ways = ways * inside % MOD * C(total + sz, sz) % MOD;
total += sz;
}
}
return memo[k] = {total, ways};
};
cout << solve(0, n + 1).second << '\n';
}
}