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.
First ignore subsequences and ask: given a candidate , what inequalities must its witnessing permutation satisfy? For , must be smaller than everything in positions , and if , position must be smaller than .
Those inequalities force a clean characterization: if , then and every value from positions through is at least . The converse is also true; sorting indices by the pair gives a valid permutation.
Think of each value as opening a block at subsequence position . Later occurrences of may extend that block, but while it is open you are not allowed to take values smaller than .
For an interval DP, let mean: using and as the endpoints of one block with value , what is the maximum total length, assuming we already had a good prefix of length before ?
When scanning a fixed left endpoint , keep the current best block length cur. For every larger value , store the earliest position where a -block could legally start, meaning cur >= v-1; when another appears, transition with that precomputed block.
The permutation is bait. Useful bait, but still bait. The real job is to understand which arrays are good without ever constructing during the DP.
Characterizing good arrays
Fix a candidate array of length . Suppose . From the definition:
Now assume . Then must equal . If , the rule for position would force . But from position , we also have and . That gives , which is nonsense.
Also, every value in positions must be at least . If some with had , then the interval for position includes , so . But position says , and again . Contradiction.
So we get the necessary condition:
For every with , either , or and .
This condition is also sufficient. Assign permutation values by sorting indices by the pair , smaller pair gets smaller . For , every in has ; if , then makes smaller than . Thus for all in . Also, if , then , so . That exactly matches the required smallest left endpoint.
So the characterization is done.
Blocks
The condition says this:
If a value appears at subsequence position , it may appear again later. But between that first and any later , all chosen values must be at least .
So each value creates a block. Larger values can form nested blocks inside it. Smaller values cannot appear inside it. That is the whole problem hiding under a trench coat.
One more important detail: to place value as the first element of an -block, the current good sequence must already have length at least . If it has more, we can take a prefix of length , because every prefix of a good sequence is good.
DP state
Let be meaningful when .
is the maximum total length of a good sequence after completing an -block that starts at original position and ends at original position , assuming we already had a good prefix of length before taking .
This is why , not . We are counting the total length after placing value at subsequence position .
We compute for from right to left, so any nested block starting later has already been computed.
Computing one row
Fix , and let . Maintain cur, the best total length of an open -block after scanning some prefix to the right of .
Initially, cur = x.
Now scan :
cur++, and set .first[v].When we are at position with value and first[v] exists, the nested block from first[v] to is usable, so:
.
Why is earliest start enough? Same required prefix length, more available elements between start and end. A later start cannot magically be better. CP problems love pretending this is subtle; it really is just monotonicity doing its job.
Top level
The final answer is the same process without an outer block. Start with ans=0. While scanning positions from left to right, value can start its first block once ans >= v-1. Keep earliest feasible first[v]; whenever another appears, update:
.
This also naturally handles the empty answer. For example, an array of all s gives , because there is no way to create the required first position before placing a .
Complexity
For every left endpoint we do one linear scan, so the time is per test case. The DP has useful entries. Since the answer is at most , each entry fits in int16_t, giving memory with a comfortable constant.
#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);
for (int i = 1; i <= n; i++) cin >> a[i];
vector<int> base(n + 2, 0);
for (int i = 1; i <= n; i++) {
base[i + 1] = base[i] + (n - i + 1);
}
vector<int16_t> dp(base[n + 1], 0);
auto getDP = [&](int l, int r) -> int {
return (int)dp[base[l] + (r - l)];
};
auto setDP = [&](int l, int r, int value) {
dp[base[l] + (r - l)] = (int16_t)value;
};
vector<int> first(n + 2, 0), seen(n + 2, 0);
int stamp = 0;
for (int l = n; l >= 1; l--) {
++stamp;
int x = a[l];
int cur = x;
setDP(l, l, cur);
for (int r = l + 1; r <= n; r++) {
int v = a[r];
if (v < x) continue;
if (v == x) {
++cur;
setDP(l, r, cur);
} else {
if (seen[v] != stamp && v <= cur + 1) {
seen[v] = stamp;
first[v] = r;
}
if (seen[v] == stamp) {
cur = max(cur, getDP(first[v], r));
}
}
}
}
++stamp;
int ans = 0;
for (int r = 1; r <= n; r++) {
int v = a[r];
if (seen[v] != stamp && v <= ans + 1) {
seen[v] = stamp;
first[v] = r;
}
if (seen[v] == stamp) {
ans = max(ans, getDP(first[v], r));
}
}
cout << ans << '\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);
for (int i = 1; i <= n; i++) cin >> a[i];
vector<int> base(n + 2, 0);
for (int i = 1; i <= n; i++) {
base[i + 1] = base[i] + (n - i + 1);
}
vector<int16_t> dp(base[n + 1], 0);
auto getDP = [&](int l, int r) -> int {
return (int)dp[base[l] + (r - l)];
};
auto setDP = [&](int l, int r, int value) {
dp[base[l] + (r - l)] = (int16_t)value;
};
vector<int> first(n + 2, 0), seen(n + 2, 0);
int stamp = 0;
for (int l = n; l >= 1; l--) {
++stamp;
int x = a[l];
int cur = x;
setDP(l, l, cur);
for (int r = l + 1; r <= n; r++) {
int v = a[r];
if (v < x) continue;
if (v == x) {
++cur;
setDP(l, r, cur);
} else {
if (seen[v] != stamp && v <= cur + 1) {
seen[v] = stamp;
first[v] = r;
}
if (seen[v] == stamp) {
cur = max(cur, getDP(first[v], r));
}
}
}
}
++stamp;
int ans = 0;
for (int r = 1; r <= n; r++) {
int v = a[r];
if (seen[v] != stamp && v <= ans + 1) {
seen[v] = stamp;
first[v] = r;
}
if (seen[v] == stamp) {
ans = max(ans, getDP(first[v], r));
}
}
cout << ans << '\n';
}
int main() {
setIO();
int t;
cin >> t;
while (t--) solve();
}