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.
The operation only ever touches three consecutive values. If the left value is , the two later values must be exactly and in some order.
Think in terms of the smallest value you have not fixed yet. Once you decide some position becomes that value, the rest of the array behaves like that position/value was deleted and all larger remaining values were compressed.
For an alive element, define its rank as the number of alive values . An element can be pulled down to the current smallest value only if its rank is odd, because each operation decreases its rank by exactly .
When the leftmost alive position has even rank, it cannot get the current smallest value. The best it can do is the next value. So first you need to place the current smallest value at some later prefix-minimum element with odd rank.
The needed later element is found by repeatedly jumping to the alive element with the smallest index among values smaller than the current one. Use a Fenwick tree for ranks and a segment tree over inverse positions to make those jumps fast.
Let’s strip the operation down to what it actually says.
If we choose , then
So the three values are exactly
with at the leftmost chosen index. After the operation, the value at that left index drops by , and the other two values each go up by .
The key thing: the chosen left position only changes by moving down by two ranks among the still-active values. That parity fact is the whole problem. Everything else is data structures and not getting lost in the weeds.
Compress the unfinished problem
Suppose we have already fixed values in the answer. Call the remaining positions/values alive.
Among alive values, define
So the smallest alive value has rank , the next has rank , and so on.
When an alive element is used as the left element of an operation, its rank decreases by . Therefore:
That last bullet matters a lot.
Also, if we successfully make some alive position become the current answer value , then for the remaining positions we can just delete that value from the active set. The exact chain of operations shifts the lower values upward, but after compression the remaining problem is the same kind of problem again. Nice little disappearing act, but legal.
Process positions left to right
At any moment, the first unassigned position is the lexicographically most important one. Call it .
Let .
If is odd, then can be pulled all the way down to the current smallest answer value . Since is the leftmost alive position, every other alive position is to its right, so all required helper values are available on the right. We set
remove from the alive values, and continue.
If is even, then cannot become . No amount of wishful thinking fixes parity. The best possible value at is .
To make that happen, we first need some later position to become , then after removing that smaller value, the rank of becomes odd, so can become .
So now the question is: which ?
A position can be pulled down to the current smallest value only if:
That means must be on the chain of prefix minima by value. Starting from , repeatedly jump to the first alive position to the right whose value is smaller. Stop when its rank is odd.
This gives the leftmost feasible , so we set
remove both corresponding values, and continue.
Why is this lexicographically optimal?
That’s the greedy. It is annoyingly elegant, which is exactly how 3000-rated problems like to be.
Finding the jumps fast
We need two operations:
Use:
Then:
and the next smaller alive position is
which is a range minimum query on the segment tree.
Whenever a value is assigned/removed, update both structures.
Amortized bound
Each position is processed as the leftmost alive position at most once. During the inner jumping chain, an index skipped with even rank becomes odd after a smaller value to its right gets removed, so it will not keep causing chaos forever. Each index is visited only times.
Each visit costs , so the total complexity is
per test case, with total .
Memory is .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Fenwick {
int n;
vector<int> bit;
Fenwick(int n = 0) { init(n); }
void init(int n_) {
n = n_;
bit.assign(n + 1, 0);
}
void add(int idx, int delta) {
for (; idx <= n; idx += idx & -idx) bit[idx] += delta;
}
int sum(int idx) const {
int res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
};
struct SegTree {
static constexpr int INF = 1e9;
int n;
vector<int> seg;
SegTree(int n = 0) { init(n); }
void init(int n_) {
n = 1;
while (n < n_) n <<= 1;
seg.assign(2 * n, INF);
}
void setval(int idx, int val) {
idx += n - 1;
seg[idx] = val;
for (idx >>= 1; idx; idx >>= 1) {
seg[idx] = min(seg[idx << 1], seg[idx << 1 | 1]);
}
}
int query(int l, int r) const {
if (l > r) return INF;
l += n - 1;
r += n - 1;
int res = INF;
while (l <= r) {
if (l & 1) res = min(res, seg[l++]);
if (!(r & 1)) res = min(res, seg[r--]);
l >>= 1;
r >>= 1;
}
return res;
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> p(n), pos(n + 1);
for (int i = 0; i < n; i++) {
cin >> p[i];
pos[p[i]] = i;
}
Fenwick fw(n);
SegTree seg(n);
for (int v = 1; v <= n; v++) {
fw.add(v, 1);
seg.setval(v, pos[v]);
}
vector<int> ans(n, 0);
int cur = 1;
auto remove_value = [&](int v) {
fw.add(v, -1);
seg.setval(v, SegTree::INF);
};
for (int i = 0; i < n; i++) {
if (ans[i]) continue;
int rank = fw.sum(p[i]);
if (rank % 2 == 1) {
ans[i] = cur++;
remove_value(p[i]);
} else {
int j = i;
while (true) {
j = seg.query(1, p[j] - 1);
if (fw.sum(p[j]) % 2 == 1) break;
}
ans[j] = cur;
ans[i] = cur + 1;
cur += 2;
remove_value(p[i]);
remove_value(p[j]);
}
}
for (int i = 0; i < n; i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Fenwick {
int n;
vector<int> bit;
Fenwick(int n = 0) { init(n); }
void init(int n_) {
n = n_;
bit.assign(n + 1, 0);
}
void add(int idx, int delta) {
for (; idx <= n; idx += idx & -idx) bit[idx] += delta;
}
int sum(int idx) const {
int res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
};
struct SegTree {
static constexpr int INF = 1e9;
int n;
vector<int> seg;
SegTree(int n = 0) { init(n); }
void init(int n_) {
n = 1;
while (n < n_) n <<= 1;
seg.assign(2 * n, INF);
}
void setval(int idx, int val) {
idx += n - 1;
seg[idx] = val;
for (idx >>= 1; idx; idx >>= 1) {
seg[idx] = min(seg[idx << 1], seg[idx << 1 | 1]);
}
}
int query(int l, int r) const {
if (l > r) return INF;
l += n - 1;
r += n - 1;
int res = INF;
while (l <= r) {
if (l & 1) res = min(res, seg[l++]);
if (!(r & 1)) res = min(res, seg[r--]);
l >>= 1;
r >>= 1;
}
return res;
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> p(n), pos(n + 1);
for (int i = 0; i < n; i++) {
cin >> p[i];
pos[p[i]] = i;
}
Fenwick fw(n);
SegTree seg(n);
for (int v = 1; v <= n; v++) {
fw.add(v, 1);
seg.setval(v, pos[v]);
}
vector<int> ans(n, 0);
int cur = 1;
auto remove_value = [&](int v) {
fw.add(v, -1);
seg.setval(v, SegTree::INF);
};
for (int i = 0; i < n; i++) {
if (ans[i]) continue;
int rank = fw.sum(p[i]);
if (rank % 2 == 1) {
ans[i] = cur++;
remove_value(p[i]);
} else {
int j = i;
while (true) {
j = seg.query(1, p[j] - 1);
if (fw.sum(p[j]) % 2 == 1) break;
}
ans[j] = cur;
ans[i] = cur + 1;
cur += 2;
remove_value(p[i]);
remove_value(p[j]);
}
}
for (int i = 0; i < n; i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
}