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.
Think in terms of prefix XORs. If and , then query tells you the highest bit where and differ.
For any bit , the answer is at least exactly when the two prefix XORs have different values after shifting right by . So each answer can be found by knowing how the prefix values are partitioned by their high bits.
The whole problem becomes: reconstruct the binary trie structure of the prefix XORs, without knowing their actual values. A query between two prefix indices tells you the level of their least common ancestor in that trie.
Use divide and conquer on a set of prefix indices. Pick a pivot . Query against every other index in the current set, preferably making these intervals long by choosing the pivot near an end. The maximum returned bit identifies the top split of this set; all indices whose query to gives that maximum lie on the opposite side from .
After splitting a group by its highest differing bit, every cross-pair inside the two parts has that bit as its answer. Then recurse on each part for lower bits. Randomness makes collisions rare enough that singleton/small base cases can be handled directly; the cost stays tiny because the recursion mostly uses long intervals.
We need answer every interval, but querying every interval is financial suicide: short intervals cost too much. The trick is that the judge array is random, so the prefix XORs behave like random points in a 30-bit cube.
Let
Then
So every required answer is just the highest bit where two prefix XORs differ:
Now forget the original array. We have hidden 30-bit strings . A query between prefix indices costs and returns . We need all pair answers.
Trie View
Put the hidden prefix values into a binary trie, from bit down to bit .
For two values , is exactly the first trie level where their paths split.
That means:
So if we can recover the trie partition structure, we can fill the whole answer table.
How To Split A Group
Suppose we have a set of prefix indices that are known to share all bits above the current top differing bit. We want to find their next split.
Pick a pivot . Query with every other element .
Let
If no such exists, the group has size .
Otherwise, is the highest bit where this group differs. The pivot is on one side of that split. Every with query result is on the opposite side, because and first differ exactly at this top split bit. Every with a smaller answer is on the same side as the pivot.
So we split:
same: pivot plus all with result ;other: all with result .Then every cross pair between same and other has answer . Fill those cells immediately.
Then recurse on both parts.
That is the whole algorithm. Pretty clean, once you stop trying to recover the actual numbers.
Why The Cost Is Okay
A query between prefix indices and corresponds to original interval , so its cost is
For each recursive group, we choose the pivot as one of the two extreme indices of the group. Specifically, we try both the minimum and maximum index and take the one with smaller total query cost to the other elements.
That keeps most queried distances large. Since , and the hidden values are random, trie splits are usually balanced-ish. The recursion therefore uses only a few hundred cheap long-distance queries per test. Across tests this fits the robocoin budget.
The Example Is Special
The sample has and is not random. We can just query all missing pairs directly. The total cost is tiny, and it avoids depending on randomness for the sample. For , the official tests are random, which is the entire point of this problem.
Correctness Argument
For a query between prefix indices and , the returned value is by definition of prefix XOR. Thus solving all original queries is equivalent to filling this value for all pairs of prefix indices.
Consider any recursive group where all values share all bits above the current top differing bit. Pick pivot and query it against every other element of . Let be the maximum returned value. No pair in can differ at a bit above , otherwise the pivot would differ from at least one side at that bit too. Therefore is the highest bit at which the values in differ.
At bit , the pivot has one bit value. An element has the opposite value at bit exactly when . If the answer is smaller, then matches the pivot at bit . Therefore the split produced by the algorithm is exactly the trie split of at bit .
Every pair crossing the two split parts differs first at bit , so assigning answer to all cross pairs is correct. Pairs inside each part agree at bit and all higher bits, so their answers are lower and are handled recursively. The base case of one element has no pairs to fill. By induction over the recursion tree, all pair answers are filled correctly.
Finally, the output for interval is the pair answer for prefix indices , so the printed table is correct.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Solver {
int n;
vector<vector<int>> ans;
int ask(int i, int j) {
if (i > j) swap(i, j);
if (ans[i][j] != -2) return ans[i][j];
cout << "? " << i + 1 << ' ' << j << endl;
cout.flush();
int x;
cin >> x;
if (x == -2) exit(0);
ans[i][j] = ans[j][i] = x;
return x;
}
double costFromPivot(const vector<int>& v, int p) {
double res = 0.0;
for (int x : v) {
if (x != p) res += 1.0 / abs(x - p);
}
return res;
}
void solveSet(vector<int> v) {
if (v.size() <= 1) return;
if (v.size() == 2) {
ask(v[0], v[1]);
return;
}
int mn = *min_element(v.begin(), v.end());
int mxid = *max_element(v.begin(), v.end());
int root = costFromPivot(v, mn) <= costFromPivot(v, mxid) ? mn : mxid;
vector<pair<int,int>> got;
int best = -1;
for (int x : v) {
if (x == root) continue;
int q = ask(root, x);
got.push_back({x, q});
best = max(best, q);
}
vector<int> same, other;
same.push_back(root);
for (auto [x, q] : got) {
if (q == best) other.push_back(x);
else same.push_back(x);
}
for (int x : same) {
for (int y : other) {
ans[x][y] = ans[y][x] = best;
}
}
solveSet(same);
solveSet(other);
}
void solveCase() {
ans.assign(n + 1, vector<int>(n + 1, -2));
for (int i = 0; i <= n; i++) ans[i][i] = -1;
if (n == 3) {
for (int i = 0; i <= n; i++) {
for (int j = i + 1; j <= n; j++) ask(i, j);
}
} else {
vector<int> all(n + 1);
iota(all.begin(), all.end(), 0);
solveSet(all);
}
cout << "!" << '\n';
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
cout << ans[i - 1][j] << (j == n ? '\n' : ' ');
}
}
cout.flush();
}
};
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n == -2) return 0;
Solver s;
s.n = n;
s.solveCase();
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Solver {
int n;
vector<vector<int>> ans;
int ask(int i, int j) {
if (i > j) swap(i, j);
if (ans[i][j] != -2) return ans[i][j];
cout << "? " << i + 1 << ' ' << j << endl;
cout.flush();
int x;
cin >> x;
if (x == -2) exit(0);
ans[i][j] = ans[j][i] = x;
return x;
}
double costFromPivot(const vector<int>& v, int p) {
double res = 0.0;
for (int x : v) {
if (x != p) res += 1.0 / abs(x - p);
}
return res;
}
void solveSet(vector<int> v) {
if (v.size() <= 1) return;
if (v.size() == 2) {
ask(v[0], v[1]);
return;
}
int mn = *min_element(v.begin(), v.end());
int mxid = *max_element(v.begin(), v.end());
int root = costFromPivot(v, mn) <= costFromPivot(v, mxid) ? mn : mxid;
vector<pair<int,int>> got;
int best = -1;
for (int x : v) {
if (x == root) continue;
int q = ask(root, x);
got.push_back({x, q});
best = max(best, q);
}
vector<int> same, other;
same.push_back(root);
for (auto [x, q] : got) {
if (q == best) other.push_back(x);
else same.push_back(x);
}
for (int x : same) {
for (int y : other) {
ans[x][y] = ans[y][x] = best;
}
}
solveSet(same);
solveSet(other);
}
void solveCase() {
ans.assign(n + 1, vector<int>(n + 1, -2));
for (int i = 0; i <= n; i++) ans[i][i] = -1;
if (n == 3) {
for (int i = 0; i <= n; i++) {
for (int j = i + 1; j <= n; j++) ask(i, j);
}
} else {
vector<int> all(n + 1);
iota(all.begin(), all.end(), 0);
solveSet(all);
}
cout << "!" << '\n';
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
cout << ans[i - 1][j] << (j == n ? '\n' : ' ');
}
}
cout.flush();
}
};
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
if (n == -2) return 0;
Solver s;
s.n = n;
s.solveCase();
}
return 0;
}