| #include <bits/stdc++.h> |
| using namespace std; |
|
|
| using ll = long long; |
|
|
| struct SplitMix64Hash { |
| static uint64_t splitmix64(uint64_t x) { |
| x += 0x9e3779b97f4a7c15ULL; |
| x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; |
| x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL; |
| return x ^ (x >> 31); |
| } |
| size_t operator()(uint64_t x) const { |
| static const uint64_t FIXED_RANDOM = |
| chrono::steady_clock::now().time_since_epoch().count(); |
| return (size_t)splitmix64(x + FIXED_RANDOM); |
| } |
| }; |
|
|
| struct Edge { |
| int u, v; |
| ll w; |
| }; |
|
|
| static constexpr ll KEY_BASE = 131072LL; |
|
|
| struct Solver { |
| int n = 0; |
| unordered_map<uint64_t, ll, SplitMix64Hash> cache; |
| vector<Edge> edges; |
|
|
| ll ask(int u, int v) { |
| if (u > v) swap(u, v); |
| uint64_t key = (uint64_t)u * (uint64_t)KEY_BASE + (uint64_t)v; |
| auto it = cache.find(key); |
| if (it != cache.end()) return it->second; |
|
|
| cout << "? " << u << " " << v << "\n"; |
| cout.flush(); |
|
|
| ll ans; |
| if (!(cin >> ans)) exit(0); |
| if (ans < 0) exit(0); |
|
|
| if (cache.size() < 2000000) cache.emplace(key, ans); |
| return ans; |
| } |
|
|
| vector<ll> queryAll(int src, const vector<int>& nodes) { |
| vector<ll> d(nodes.size()); |
| for (size_t i = 0; i < nodes.size(); i++) { |
| int v = nodes[i]; |
| d[i] = (v == src ? 0LL : ask(src, v)); |
| } |
| return d; |
| } |
|
|
| struct Comp { |
| vector<int> nodes; |
| bool knownRootDist = false; |
| int root = -1; |
| vector<ll> distRoot; |
| }; |
|
|
| void processComp(Comp&& comp, vector<Comp>& st) { |
| auto& nodes = comp.nodes; |
| int sz = (int)nodes.size(); |
| if (sz <= 1) return; |
|
|
| int A = -1; |
| if (comp.knownRootDist) { |
| int bestIdx = 0; |
| ll bestD = comp.distRoot[0]; |
| for (int i = 1; i < sz; i++) { |
| if (comp.distRoot[i] > bestD) { |
| bestD = comp.distRoot[i]; |
| bestIdx = i; |
| } |
| } |
| A = nodes[bestIdx]; |
| } else { |
| int s = nodes[0]; |
| vector<ll> distS = queryAll(s, nodes); |
| int bestIdx = 0; |
| ll bestD = distS[0]; |
| for (int i = 1; i < sz; i++) { |
| if (distS[i] > bestD) { |
| bestD = distS[i]; |
| bestIdx = i; |
| } |
| } |
| A = nodes[bestIdx]; |
| } |
|
|
| vector<ll> distA = queryAll(A, nodes); |
| int bIdx = 0; |
| ll D = distA[0]; |
| for (int i = 1; i < sz; i++) { |
| if (distA[i] > D) { |
| D = distA[i]; |
| bIdx = i; |
| } |
| } |
| int B = nodes[bIdx]; |
|
|
| vector<ll> distB = queryAll(B, nodes); |
| D = distA[bIdx]; |
|
|
| vector<char> isPath(sz, 0); |
| vector<pair<ll, int>> path; |
| path.reserve(sz); |
|
|
| for (int i = 0; i < sz; i++) { |
| if (distA[i] + distB[i] == D) { |
| isPath[i] = 1; |
| path.push_back({distA[i], nodes[i]}); |
| } |
| } |
|
|
| sort(path.begin(), path.end()); |
| int m = (int)path.size(); |
|
|
| for (int i = 0; i + 1 < m; i++) { |
| int u = path[i].second; |
| int v = path[i + 1].second; |
| ll w = path[i + 1].first - path[i].first; |
| edges.push_back({u, v, w}); |
| } |
|
|
| if (m == 0) return; |
|
|
| unordered_map<ll, int, SplitMix64Hash> coord2idx; |
| coord2idx.reserve((size_t)m * 2); |
| for (int i = 0; i < m; i++) coord2idx.emplace(path[i].first, i); |
|
|
| vector<vector<int>> buckets(m); |
| vector<vector<ll>> bucketDist(m); |
|
|
| for (int i = 0; i < sz; i++) { |
| if (isPath[i]) continue; |
| ll numT = distA[i] + D - distB[i]; |
| ll numH = distA[i] + distB[i] - D; |
| ll t = numT / 2; |
| ll h = numH / 2; |
| auto it = coord2idx.find(t); |
| if (it == coord2idx.end()) continue; |
| int j = it->second; |
| buckets[j].push_back(nodes[i]); |
| bucketDist[j].push_back(h); |
| } |
|
|
| for (int j = 0; j < m; j++) { |
| if (buckets[j].empty()) continue; |
| int p = path[j].second; |
|
|
| Comp child; |
| child.knownRootDist = true; |
| child.root = p; |
|
|
| child.nodes.reserve(1 + buckets[j].size()); |
| child.distRoot.reserve(1 + buckets[j].size()); |
|
|
| child.nodes.push_back(p); |
| child.distRoot.push_back(0); |
|
|
| for (size_t k = 0; k < buckets[j].size(); k++) { |
| child.nodes.push_back(buckets[j][k]); |
| child.distRoot.push_back(bucketDist[j][k]); |
| } |
| st.push_back(std::move(child)); |
| } |
| } |
|
|
| void solveCase() { |
| edges.clear(); |
| edges.reserve(max(0, n - 1)); |
|
|
| cache.clear(); |
| size_t reserveHint = (size_t)min(2000000LL, max(0LL, 20LL * (ll)n)); |
| cache.reserve(reserveHint); |
|
|
| if (n <= 1) { |
| cout << "!\n"; |
| cout.flush(); |
| return; |
| } |
|
|
| vector<Comp> st; |
| st.reserve(n); |
|
|
| Comp root; |
| root.knownRootDist = false; |
| root.nodes.resize(n); |
| iota(root.nodes.begin(), root.nodes.end(), 1); |
| st.push_back(std::move(root)); |
|
|
| while (!st.empty()) { |
| Comp cur = std::move(st.back()); |
| st.pop_back(); |
| processComp(std::move(cur), st); |
| } |
|
|
| |
| cout << "!"; |
| for (const auto& e : edges) { |
| cout << " " << e.u << " " << e.v << " " << e.w; |
| } |
| cout << "\n"; |
| cout.flush(); |
| } |
| }; |
|
|
| int main() { |
| ios::sync_with_stdio(false); |
| cin.tie(nullptr); |
|
|
| int T; |
| if (!(cin >> T)) return 0; |
| Solver solver; |
|
|
| while (T--) { |
| cin >> solver.n; |
| solver.solveCase(); |
| } |
| return 0; |
| } |