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.
Stop thinking about individual indices first. Pair every index with its mirror , and pair every value with its mirror value . The sorted array wants each value-pair inside the matching index-pair.
If two mirror values and are currently at positions and , one query can force them to become symmetric. Try querying and check both possible coin outcomes.
For odd , the middle value is special because its mirror is itself. Move value to the middle first by repeatedly querying the middle with its current position. Each try succeeds with probability .
After every value-pair is symmetric, treat each pair as a block. To move block to target positions and , query the correct target position against the current position of the corresponding value.
For one block in phase two, the states are basically: zero correct positions, one correct position, done. From zero, one query always reaches one; from one, the next query either finishes or undoes. That gives expected queries per block, so the total expectation is . The extra is the anti-bad-luck tax.
This is interactive, so the coin is real. The correct mindset is not “force every exact swap”. You cannot. The play is to make progress in a structure that survives the mirror randomness.
Define . Position is mirrored with position , and value is mirrored with value . In the final sorted permutation, the value-pair must occupy the position-pair .
Phase 0: handle the middle
If is odd, the middle position mirrors to itself, and the middle value also mirrors to itself. So before doing pair logic, put value into position .
While , query .
The judge either swaps , which succeeds, or swaps , which does not move value . So each attempt succeeds independently with probability , and the expected cost is moves. Annoying, but tiny.
Phase 1: make every value-pair symmetric
Now process every from to . Let:
If , then values and are already in mirrored positions.
Otherwise query:
Why does this work? There are two possible swaps:
Either way, the pair becomes symmetric after exactly one query. No coin drama here. The coin tries to be clever and still gets clowned.
Also, once a processed value-pair occupies both positions of some mirror pair, later phase-one operations for different values cannot accidentally break it. To touch one of those positions, the operation would have to involve one of those already processed values or its mirror position, but that whole mirror pair is already occupied by the processed pair.
So phase one costs at most moves.
Phase 2: move symmetric blocks to their targets
After phase one, every pair is a block sitting in some mirrored position-pair. Now process and put block into positions and .
While the block is not fully correct:
Why does this make sense?
Suppose the block is currently symmetric, but not at its target. If we query , then:
So from the state where neither side is correct, one query always makes exactly one side correct.
From the state where exactly one side is correct, querying the other side has two outcomes:
Let be the expected number of moves from zero correct positions, and from one correct position. Then:
and
Solving gives . So each block costs moves in expectation during phase two. There are blocks, so this phase costs about moves in expectation.
Total expected moves:
plus the tiny middle handling when is odd. The limit is , so the extra is there to absorb unlucky coin streaks. There is no deterministic guarantee against absurd randomness, because the problem literally hands control to a fair coin. That is the whole gimmick.
Correctness summary
Phase 0 places the only self-mirrored value correctly when it exists.
Phase 1 guarantees that every mirrored value-pair occupies some mirrored pair of positions.
Phase 2 processes target pairs from outside representatives through . Once a block is fixed at its target pair, later blocks cannot touch it, because their values are elsewhere and their target pairs are different. The loop for a block ends only when both values are in their exact sorted positions. Therefore, after all blocks are processed, every position contains its own value, so the permutation is sorted.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int n;
vector<int> p, pos;
int mir(int x) {
return n - x + 1;
}
pair<int, int> ask(int x, int y) {
cout << "? " << x << ' ' << y << endl;
int u, v;
cin >> u;
if (u == -1) exit(0);
cin >> v;
return {u, v};
}
void apply_swap(int u, int v) {
swap(p[u], p[v]);
pos[p[u]] = u;
pos[p[v]] = v;
}
void move_query(int x, int y) {
auto [u, v] = ask(x, y);
apply_swap(u, v);
}
void solve() {
cin >> n;
p.assign(n + 1, 0);
pos.assign(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> p[i];
pos[p[i]] = i;
}
if (n % 2 == 1) {
int mid = (n + 1) / 2;
while (pos[mid] != mid) {
move_query(mid, pos[mid]);
}
}
for (int i = 1; i <= n / 2; i++) {
int x = pos[i];
int y = pos[mir(i)];
if (x + y != n + 1) {
move_query(x, mir(y));
}
}
for (int i = 1; i <= n / 2; i++) {
while (pos[i] != i || pos[mir(i)] != mir(i)) {
if (pos[i] != i) {
move_query(i, pos[i]);
} else {
move_query(mir(i), pos[mir(i)]);
}
}
}
cout << "!" << endl;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) solve();
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int n;
vector<int> p, pos;
int mir(int x) {
return n - x + 1;
}
pair<int, int> ask(int x, int y) {
cout << "? " << x << ' ' << y << endl;
int u, v;
cin >> u;
if (u == -1) exit(0);
cin >> v;
return {u, v};
}
void apply_swap(int u, int v) {
swap(p[u], p[v]);
pos[p[u]] = u;
pos[p[v]] = v;
}
void move_query(int x, int y) {
auto [u, v] = ask(x, y);
apply_swap(u, v);
}
void solve() {
cin >> n;
p.assign(n + 1, 0);
pos.assign(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> p[i];
pos[p[i]] = i;
}
if (n % 2 == 1) {
int mid = (n + 1) / 2;
while (pos[mid] != mid) {
move_query(mid, pos[mid]);
}
}
for (int i = 1; i <= n / 2; i++) {
int x = pos[i];
int y = pos[mir(i)];
if (x + y != n + 1) {
move_query(x, mir(y));
}
}
for (int i = 1; i <= n / 2; i++) {
while (pos[i] != i || pos[mir(i)] != mir(i)) {
if (pos[i] != i) {
move_query(i, pos[i]);
} else {
move_query(mir(i), pos[mir(i)]);
}
}
}
cout << "!" << endl;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) solve();
return 0;
}