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.
Relabel the vertices by their positions in . Then becomes the identity order, and the whole problem becomes about one permutation . Two permutations are annoying; one permutation is still annoying, but at least it is one annoying thing.
In any DFS preorder, a subtree is a contiguous segment, and its root is the first element of that segment. After relabeling, a common subtree must be a segment whose values are also one contiguous value interval, starting at .
A segment can be a common subtree exactly when and . Since all values are distinct, this means the segment contains precisely the values .
The answer is not just an LIS. A path in the tree corresponds to nested valid segments, not merely vertices appearing in the same relative order. Sample 4 is the trap: it has increasing subsequences, but no useful nested subtree blocks.
For each start , only the largest valid segment matters. These maximal segments do not cross, so after computing all , sweep left to right with a stack of right endpoints; the maximum stack size plus is the depth.
Normalize The Permutations
Let be the position of vertex in Arietta's order . Replace Tortinita's order by
.
Now Arietta's order is just , and Tortinita's order is the permutation . The root is outside this array and will be handled by adding one final edge at the end.
What A Common Subtree Looks Like
In a DFS preorder, every subtree appears as one contiguous segment, with the subtree root first.
So a segment can be one common subtree iff:
Because is a permutation, this becomes the clean condition:
and .
Call such a segment valid.
This is the core translation. Everything else is just engineering around the fact that there can be way too many valid segments. In the identity permutation, every segment is valid, so brute force is hilariously dead.
Depth Means Nesting
A root-to-leaf path in the tree gives nested subtrees. Therefore, after normalization, we want the longest chain of nested valid segments.
Conversely, if we have nested valid segments, we can realize them in an actual tree: make the next segment on the chain one child subtree, and attach everything else as singleton child subtrees. Since DFS child order is arbitrary in each sequence, this works.
Important: this is why LIS is bait. Same relative order only says ancestors appear before descendants. It does not force the descendants to stay inside one contiguous DFS subtree block. DFS is pickier than that.
Why Only The Largest Segment Per Start Matters
For each , let be the largest such that is valid. If no non-singleton valid segment starts at , set .
The useful fact is: maximal segments do not cross.
Suppose and cross, with . Since lies inside the first valid segment, lies inside its consecutive value interval. The second segment also has a consecutive value interval starting at , so the two value intervals overlap. Their position intervals overlap too, so their union also has consecutive values and still has minimum . That means is valid, contradicting the maximality of .
So the maximal valid segments form a laminar family: they are nested or disjoint. This lets us sweep them like DFS intervals.
When scanning , keep a stack of active right endpoints. Before processing , pop every endpoint . If , push it. The largest stack size is the largest number of nested non-singleton valid segments.
Why output stack_max + 1? The stack counts non-root non-singleton blocks. The real tree also has the edge from root into the first block, or directly into a vertex if there are no blocks. The innermost non-singleton block can always go one more edge to a singleton leaf. So the final answer is exactly max_stack_size + 1.
Computing All
We need the largest valid segment starting at every in .
Use divide and conquer over index ranges. For a recursive range with midpoint , recursively solve the left and right halves. Now only handle valid segments that cross the midpoint: .
Precompute:
For a crossing valid segment, must be the minimum on the left suffix. If it is not, skip .
Let:
We need , because the global minimum must be .
Now split by where the maximum lies.
Case 1: maximum is on the left.
Then the global maximum is , so:
.
Thus is forced. We just check that is in the right half, , and .
Case 2: maximum is on the right.
Then:
,
so:
.
For every right prefix endpoint , bucket it by the key . For a fixed , we only need the bucket with key . Inside that bucket, endpoints are processed in increasing order; prefix minima only decrease, and prefix maxima only increase. A moving pointer finds the largest endpoint whose right minimum is still greater than , then we check that its right maximum beats the left maximum.
Each divide-and-conquer level does linear work over its range, so the total time is .
Then the stack sweep gives the answer in .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
void solve() {
int n;
cin >> n;
vector<int> a(n + 1), pos(n + 1), c(n + 1), R(n + 2);
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
pos[x] = i;
}
for (int i = 1; i <= n; ++i) c[i] = pos[a[i]];
vector<int> mn(n + 2), mx(n + 2), ptr(2 * n + 5);
vector<vector<pair<int, int>>> bucket(2 * n + 5);
const int off = n;
auto work = [&](auto&& self, int l, int r) -> void {
if (l >= r) return;
int mid = (l + r) / 2;
self(self, l, mid);
self(self, mid + 1, r);
mn[mid] = mx[mid] = c[mid];
for (int i = mid - 1; i >= l; --i) {
mn[i] = min(mn[i + 1], c[i]);
mx[i] = max(mx[i + 1], c[i]);
}
mn[mid + 1] = mx[mid + 1] = c[mid + 1];
bucket[mx[mid + 1] - (mid + 1) + off].push_back({mn[mid + 1], mid + 1});
for (int j = mid + 2; j <= r; ++j) {
mn[j] = min(mn[j - 1], c[j]);
mx[j] = max(mx[j - 1], c[j]);
bucket[mx[j] - j + off].push_back({mn[j], j});
}
for (int i = mid; i >= l; --i) {
if (mn[i] == mn[i + 1]) continue;
int j = i + mx[i] - mn[i];
if (j > mid && j <= r && mn[j] > mn[i] && mx[j] < mx[i]) {
R[i] = max(R[i], j);
}
int key = mn[i] - i + off;
auto& v = bucket[key];
while (ptr[key] < (int)v.size() && v[ptr[key]].first > mn[i]) {
++ptr[key];
}
if (ptr[key] > 0) {
int k = v[ptr[key] - 1].second;
if (mx[k] > mx[i]) R[i] = max(R[i], k);
}
}
for (int j = mid + 1; j <= r; ++j) {
int key = mx[j] - j + off;
bucket[key].clear();
ptr[key] = 0;
}
};
work(work, 1, n);
vector<int> st;
st.reserve(n);
int best = 0;
for (int i = 1; i <= n; ++i) {
while (!st.empty() && st.back() <= i) st.pop_back();
if (R[i]) {
st.push_back(R[i]);
best = max(best, (int)st.size());
}
}
cout << best + 1 << '\n';
}
int main() {
setIO();
int T;
cin >> T;
while (T--) solve();
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
void solve() {
int n;
cin >> n;
vector<int> a(n + 1), pos(n + 1), c(n + 1), R(n + 2);
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
pos[x] = i;
}
for (int i = 1; i <= n; ++i) c[i] = pos[a[i]];
vector<int> mn(n + 2), mx(n + 2), ptr(2 * n + 5);
vector<vector<pair<int, int>>> bucket(2 * n + 5);
const int off = n;
auto work = [&](auto&& self, int l, int r) -> void {
if (l >= r) return;
int mid = (l + r) / 2;
self(self, l, mid);
self(self, mid + 1, r);
mn[mid] = mx[mid] = c[mid];
for (int i = mid - 1; i >= l; --i) {
mn[i] = min(mn[i + 1], c[i]);
mx[i] = max(mx[i + 1], c[i]);
}
mn[mid + 1] = mx[mid + 1] = c[mid + 1];
bucket[mx[mid + 1] - (mid + 1) + off].push_back({mn[mid + 1], mid + 1});
for (int j = mid + 2; j <= r; ++j) {
mn[j] = min(mn[j - 1], c[j]);
mx[j] = max(mx[j - 1], c[j]);
bucket[mx[j] - j + off].push_back({mn[j], j});
}
for (int i = mid; i >= l; --i) {
if (mn[i] == mn[i + 1]) continue;
int j = i + mx[i] - mn[i];
if (j > mid && j <= r && mn[j] > mn[i] && mx[j] < mx[i]) {
R[i] = max(R[i], j);
}
int key = mn[i] - i + off;
auto& v = bucket[key];
while (ptr[key] < (int)v.size() && v[ptr[key]].first > mn[i]) {
++ptr[key];
}
if (ptr[key] > 0) {
int k = v[ptr[key] - 1].second;
if (mx[k] > mx[i]) R[i] = max(R[i], k);
}
}
for (int j = mid + 1; j <= r; ++j) {
int key = mx[j] - j + off;
bucket[key].clear();
ptr[key] = 0;
}
};
work(work, 1, n);
vector<int> st;
st.reserve(n);
int best = 0;
for (int i = 1; i <= n; ++i) {
while (!st.empty() && st.back() <= i) st.pop_back();
if (R[i]) {
st.push_back(R[i]);
best = max(best, (int)st.size());
}
}
cout << best + 1 << '\n';
}
int main() {
setIO();
int T;
cin >> T;
while (T--) solve();
}