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 flip the goal: instead of minimizing deletions, maximize how many elements you keep. The answer is .
Sort elements as pairs by value increasing, and for equal values by position decreasing. This weird-looking tie-break is the whole trick.
For current pair , a previous kept pair with value at most is always safe, and a previous kept pair with value is always safe. Only value is dangerous.
If you append after some kept , you need . Otherwise the original array has before , which is exactly the forbidden bad pair.
Use DP over those sorted pairs. Maintain best DP for values at most , best DP already seen for value , and suffix maxima over positions for value so you can query positions greater than .
We want minimum deletions, so do the less annoying version:
find the maximum number of elements we can keep.
Then the answer is .
A kept array is bad only if there are two kept original positions with
So an earlier and a later cannot both survive.
Sort the elements
Turn each element into a pair , where is its original position.
Process pairs by:
So all copies of one value are processed from right to left.
Let for occurrence mean:
the maximum size of a valid kept set whose last processed kept pair is .
Last means last in this sorted order, not last in the original array.
Now compute the transition for current pair .
Previous value at most
Always safe. A value at most cannot differ from by exactly .
So we can use:
Previous value
Also safe. Equal values do not conflict.
Because equal values are processed by decreasing position, repeatedly extending inside the same value just keeps more copies of from right to left. Nothing illegal happens.
So we can use:
Previous value
This is the only spicy case.
If we keep and then keep , we must avoid , because that would put an earlier before a later .
So we need:
Thus we can use:
That is the whole DP. No secret fourth case hiding under the bed.
Why the decreasing tie-break matters
For one fixed value, positions are processed from right to left.
If a DP state for value ends at position , then every selected occurrence of value inside that state has position at least . Therefore, if , all selected occurrences are after the current , so appending current is safe.
Without this tie-break, the DP would start lying to your face.
Implementation
Store pos[x]: all positions where value appears, in increasing order.
Process values $x=1..n. For each value, process pos[x]` from back to front, which is decreasing position.
Maintain:
prefixBest[v]: maximum DP over all processed values at most v.sameBest: maximum DP already computed for current value x.suf[x][k]: maximum DP among occurrences pos[x][k], pos[x][k+1], ....For current position i = pos[x][k]:
cur = 1;x >= 2, try prefixBest[x - 2] + 1;sameBest + 1;x > 1, binary search in pos[x - 1] for the first position greater than i, then use the suffix maximum from there.After finishing all occurrences of value x, build suf[x] from the computed DP values and set:
Correctness proof
Take any valid kept set and sort its elements by the same rule: value increasing, decreasing position inside equal values.
Look at the final kept pair in that order. The previous kept pair, if it exists, must have value at most , exactly , or exactly .
If it is at most , appending is always safe. If it is , appending is also safe. If it is , validity forces its position to be greater than ; otherwise the original array would contain a forbidden earlier and later .
So every valid solution is covered by one of our transitions.
Conversely, every transition we allow is valid:
Therefore the DP computes the maximum number of kept elements.
Finally, output:
Complexity
Each occurrence is processed once, and each occurrence does one binary search in the previous value's position list.
Total complexity is per test case, with memory.
Quadratic DP gets cooked here; this one actually passes.
#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<vector<int>> pos(n + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
pos[x].push_back(i);
}
vector<vector<int>> suf(n + 1);
vector<int> prefixBest(n + 1, 0);
int best = 0;
for (int x = 1; x <= n; x++) {
int m = (int)pos[x].size();
vector<int> dp(m, 0);
int sameBest = 0;
for (int k = m - 1; k >= 0; k--) {
int i = pos[x][k];
int cur = 1;
if (x >= 2) cur = max(cur, prefixBest[x - 2] + 1);
cur = max(cur, sameBest + 1);
if (x > 1) {
int id = upper_bound(pos[x - 1].begin(), pos[x - 1].end(), i) - pos[x - 1].begin();
if (id < (int)pos[x - 1].size()) {
cur = max(cur, suf[x - 1][id] + 1);
}
}
dp[k] = cur;
sameBest = max(sameBest, cur);
best = max(best, cur);
}
if (m > 0) {
suf[x].assign(m + 1, 0);
for (int k = m - 1; k >= 0; k--) {
suf[x][k] = max(suf[x][k + 1], dp[k]);
}
}
prefixBest[x] = max(prefixBest[x - 1], sameBest);
}
cout << n - best << '\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<vector<int>> pos(n + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
pos[x].push_back(i);
}
vector<vector<int>> suf(n + 1);
vector<int> prefixBest(n + 1, 0);
int best = 0;
for (int x = 1; x <= n; x++) {
int m = (int)pos[x].size();
vector<int> dp(m, 0);
int sameBest = 0;
for (int k = m - 1; k >= 0; k--) {
int i = pos[x][k];
int cur = 1;
if (x >= 2) cur = max(cur, prefixBest[x - 2] + 1);
cur = max(cur, sameBest + 1);
if (x > 1) {
int id = upper_bound(pos[x - 1].begin(), pos[x - 1].end(), i) - pos[x - 1].begin();
if (id < (int)pos[x - 1].size()) {
cur = max(cur, suf[x - 1][id] + 1);
}
}
dp[k] = cur;
sameBest = max(sameBest, cur);
best = max(best, cur);
}
if (m > 0) {
suf[x].assign(m + 1, 0);
for (int k = m - 1; k >= 0; k--) {
suf[x][k] = max(suf[x][k + 1], dp[k]);
}
}
prefixBest[x] = max(prefixBest[x - 1], sameBest);
}
cout << n - best << '\n';
}
return 0;
}