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 partition into subsequences is the same thing as choosing some directed links with and . Every element can have at most one outgoing link and at most one incoming link.
So the answer is , where is the maximum number of links you can choose. That is exactly a maximum matching: left copy = outgoing endpoint, right copy = incoming endpoint.
Instead of building the huge matching graph, look at Hall deficiency. The number of unmatched right vertices is . For any fixed value , an optimal takes a prefix of the occurrences of , never a random scattered subset.
Let be how many first occurrences of value are chosen into . The contribution is minus, for every value , the number of -occurrences before the latest chosen occurrence among values and .
That formula splits by parity. Even variables only interact through odd middle values, and odd variables only interact through even middle values. Do a DP along values of one parity; optimize each transition using prefix/suffix maxima over .
We need partition the array into the fewest subsequences where adjacent picked values differ by exactly .
The tempting idea is to scan left to right and attach the current value to any existing subsequence ending at or . That is not safe. Example: . If you waste the only tail at on the first , then the later is screwed. Optimal tie-breaking is not obvious, so a plain greedy is playing with fire.
Turn paths into matching
Think of each subsequence as a path through positions. If two consecutive elements in a subsequence are positions and , then we choose a directed link
Each position can have at most one outgoing link and at most one incoming link. If we choose links total, then those links merge singleton paths into paths. So minimizing the number of subsequences is the same as maximizing the number of chosen links.
Build a bipartite graph:
A valid set of links is exactly a matching in this graph. Therefore
Equivalently, it is the minimum number of unmatched right vertices in a maximum matching.
Use Hall deficiency
For a bipartite graph with right side , the minimum possible number of unmatched right vertices is
So we need compute this maximum without touching the massive edge set.
Here is the key structure. Suppose contains some later occurrence of value , but skips an earlier occurrence of the same value. Add that earlier occurrence too. Its possible left-neighbors are all earlier adjacent values, and every one of those is also a neighbor of the later occurrence. So adding it increases by and does not increase .
That means an optimal chooses a prefix of occurrences for every value.
Let be the number of first occurrences of value chosen into . Let be the position of the -th occurrence of value , and treat as before the array.
For a fixed value , which left vertices of value are in ? A position with value is a neighbor if there is a chosen right vertex after it with value or .
So only the latest chosen occurrence among values and matters:
The number of value- left vertices added to is the number of occurrences of before .
Thus the deficiency is
Now the problem is just maximizing this expression.
Parity split
The penalty for value depends on and . Those two values have the same parity.
So even variables only interact through odd middle values, and odd variables only interact through even middle values. The total expression splits into two independent DPs. The final answer is:
DP over one parity
Take one parity. Its values form a chain:
Between two neighboring same-parity values and , the middle value is .
Suppose the DP currently stores : the best value so far if we chose occurrences of value .
Now transition to value , choosing of its occurrences. We gain , because those are added to . We pay the middle penalty:
So:
Naively this is quadratic between adjacent value buckets. That is where the greedy-ish optimization shows up.
Define:
Then
For fixed , let . Then:
has two cases:
The arrays and are nondecreasing because occurrence positions are sorted. So while scanning from upward, we maintain:
That gives each transition in linear time in the sizes of the three involved buckets.
We add dummy values outside the real range with count so boundary penalties are handled naturally. Total complexity is per test case, which is fine since and total is .
No giant matching graph. No sketchy online tie-breaks. Just Hall, parity, DP, and a little sweep. Clean enough to not get murdered by a 2500.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int NEG = -1'000'000'000;
void transition_dp(
vector<int>& dp,
vector<int>& ndp,
vector<int>& A,
vector<int>& suf,
const vector<int>& left,
const vector<int>& mid,
const vector<int>& right
) {
int L = (int)left.size();
int R = (int)right.size();
A.assign(L + 1, 0);
int p = 0;
for (int c = 1; c <= L; c++) {
while (p < (int)mid.size() && mid[p] < left[c - 1]) p++;
A[c] = p;
}
suf.assign(L + 2, NEG);
for (int c = L; c >= 0; c--) {
suf[c] = max(suf[c + 1], dp[c] - A[c]);
}
ndp.assign(R + 1, NEG);
int pref = NEG;
int ptr = 0;
p = 0;
for (int d = 0; d <= R; d++) {
if (d > 0) {
while (p < (int)mid.size() && mid[p] < right[d - 1]) p++;
}
int b = p;
while (ptr <= L && A[ptr] <= b) {
pref = max(pref, dp[ptr]);
ptr++;
}
int best = max(pref - b, suf[ptr]);
ndp[d] = d + best;
}
dp.swap(ndp);
}
int solve_component(int parity, int V, const vector<vector<int>>& pos) {
const vector<int> empty;
auto get = [&](int x) -> const vector<int>& {
if (1 <= x && x <= V) return pos[x];
return empty;
};
int start = (parity == 0 ? 0 : -1);
int finish = (parity == 0 ? V + 2 : V + 1);
vector<int> dp(1, 0), ndp, A, suf;
int left_value = start;
for (int right_value = start + 2; right_value <= finish; right_value += 2) {
int mid_value = left_value + 1;
transition_dp(dp, ndp, A, suf, get(left_value), get(mid_value), get(right_value));
left_value = right_value;
}
return dp[0];
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
int V = 2 * n;
vector<vector<int>> pos(V + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
pos[x].push_back(i);
}
int ans = solve_component(0, V, pos) + solve_component(1, V, pos);
cout << ans << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int NEG = -1'000'000'000;
void transition_dp(
vector<int>& dp,
vector<int>& ndp,
vector<int>& A,
vector<int>& suf,
const vector<int>& left,
const vector<int>& mid,
const vector<int>& right
) {
int L = (int)left.size();
int R = (int)right.size();
A.assign(L + 1, 0);
int p = 0;
for (int c = 1; c <= L; c++) {
while (p < (int)mid.size() && mid[p] < left[c - 1]) p++;
A[c] = p;
}
suf.assign(L + 2, NEG);
for (int c = L; c >= 0; c--) {
suf[c] = max(suf[c + 1], dp[c] - A[c]);
}
ndp.assign(R + 1, NEG);
int pref = NEG;
int ptr = 0;
p = 0;
for (int d = 0; d <= R; d++) {
if (d > 0) {
while (p < (int)mid.size() && mid[p] < right[d - 1]) p++;
}
int b = p;
while (ptr <= L && A[ptr] <= b) {
pref = max(pref, dp[ptr]);
ptr++;
}
int best = max(pref - b, suf[ptr]);
ndp[d] = d + best;
}
dp.swap(ndp);
}
int solve_component(int parity, int V, const vector<vector<int>>& pos) {
const vector<int> empty;
auto get = [&](int x) -> const vector<int>& {
if (1 <= x && x <= V) return pos[x];
return empty;
};
int start = (parity == 0 ? 0 : -1);
int finish = (parity == 0 ? V + 2 : V + 1);
vector<int> dp(1, 0), ndp, A, suf;
int left_value = start;
for (int right_value = start + 2; right_value <= finish; right_value += 2) {
int mid_value = left_value + 1;
transition_dp(dp, ndp, A, suf, get(left_value), get(mid_value), get(right_value));
left_value = right_value;
}
return dp[0];
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
int V = 2 * n;
vector<vector<int>> pos(V + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
pos[x].push_back(i);
}
int ans = solve_component(0, V, pos) + solve_component(1, V, pos);
cout << ans << '\n';
}
return 0;
}