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.
A neat subsequence is just several chunks placed one after another. Each chunk is forced: if its value is , then it must contain exactly copies of .
Think prefix DP. Let be the best neat subsequence length using only the first elements. You can always skip , so .
If is used as the last element of a new block, then that block needs occurrences of value ending at position .
When the current occurrence of is the -th one, a block ending here can exist only if . The latest possible first occurrence of that block is the -th occurrence of .
Use the last occurrences of value as the new block. If their first position is , transition with .
The whole problem is about realizing that blocks are extremely rigid. A block cannot be “some equal values”; if the value is , the block must contain exactly copies of . So every block has the form:
We need the longest subsequence that can be split into such blocks.
DP State
Let be the maximum length of a neat subsequence using only the prefix .
There is always the boring option: ignore .
Now suppose we actually use . Let . If is the final element of the last block, then the last block must be exactly selected occurrences of value .
So the question becomes: do we have occurrences of value ending at position ?
Tracking Occurrences
For every value , store all positions where appeared so far.
When we are at position with value , append to pos[x]. Let be the number of occurrences of seen so far.
If , then we cannot form a block of value ending here. Not enough copies. Brutal, but fair.
If , then we can use the last occurrences of as one block.
Let their first position be . In zero-indexed occurrence-list terms, that is:
Then everything before this block must come from positions before , so the previous best value is . Adding this new block gives:
Why The Last Occurrences Are Best
This is the key little trick.
Imagine a block of value ending at the current position. Its first selected occurrence must be early enough so that there are at least occurrences of from there through the current position.
Among all valid starts, we want the latest possible start, because then the prefix before the block is as large as possible. Since over prefixes never decreases, a later start can only help or tie.
The latest possible start is exactly the first of the last occurrences of .
So we never need to try all previous occurrences. No nested loop, no drama, no accidental faceplant.
Algorithm
For each test case:
pos[value], the positions where each value appeared.pos[x].pos[x].size() >= x, let be the first position among the last occurrences of .Correctness Proof
We prove that is the best possible neat subsequence length inside the first elements.
Base case: , and the empty array is neat, so this is correct.
For the transition, take an optimal neat subsequence inside the first elements.
If it does not use , then it lies entirely in the first elements, so its length is at most , which our transition considers.
Otherwise, consider the last block of this subsequence. Since it ends at , that block must contain exactly selected occurrences of value . Therefore, at least occurrences of must have appeared up to position .
Let be the first position among the last occurrences of up to . Any valid block ending at must start no later than , because after there are fewer than occurrences of . Since prefix DP is nondecreasing, choosing the block to start at gives a previous prefix value at least as good as any earlier valid start. Thus the best block ending at contributes exactly , which our transition considers.
So every optimal solution is covered by either skipping or ending a block at . Therefore is correct.
By induction, is the length of the longest neat subsequence.
Complexity
Each position is appended once and processed once.
Time complexity: per test case.
Memory complexity: per test case.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> dp(n + 1, 0);
vector<vector<int>> pos(n + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
dp[i] = dp[i - 1];
pos[x].push_back(i);
int m = (int)pos[x].size();
if (m >= x) {
int start = pos[x][m - x];
dp[i] = max(dp[i], dp[start - 1] + x);
}
}
cout << dp[n] << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> dp(n + 1, 0);
vector<vector<int>> pos(n + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
dp[i] = dp[i - 1];
pos[x].push_back(i);
int m = (int)pos[x].size();
if (m >= x) {
int start = pos[x][m - x];
dp[i] = max(dp[i], dp[start - 1] + x);
}
}
cout << dp[n] << '\n';
}
return 0;
}