J. Travel 2
思维,模拟搜索。
如果从 \(u\) 选一条边到 \(v\),然后再从 \(v\) 又刚好选到一条边回来 \(u\),那么 \(u-v\) 这条边我们已经知道它分别在 \(u\) 和 \(v\) 里的排名了,一共有 \(m\) 条边,显然 \(2m\) 次可以拿来确定有哪些边。
一开始什么都不知道,考虑从 \(1\) 的第 \(1\) 条边一直出发,以此到 \(v_1,v_2,...\) 的第一条边去找,假如某次询问后又回到了上一个点,那么就从上一个点的第二条边出发,以此往复。
假如某次询问后回到 \(u\) 这个点,而这个点已经已经确定了所有的边,那么可以把这个点标记,接下来去从它所连向的点中找没有确定完所有边的点 \(v\) 去进行上面的搜索过程,确定完 \(v\) 的所有边后再从 \(v\) 回溯到 \(u\),再去找下一个点。
手动模拟一下可以发现的是,除去那 \(2m\) 条确定边的过程中,已经确定完所有边的 \(u\) 去找未确定完所有边的 \(v\) 的过程,就是以 \(u\) 为 \(v\) 的父节点的图中的一颗生成树的树边,而这样的查询过程,向下只会找 \(n-1\) 次,向上回溯需要 \(n-1\) 次,总次数 \(2m+2(n-1)\) 次。
点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {const int N = 2500;std::vector<int> d(N);auto move = [&](int i)->int{std::cout << "> " << i << std::endl;int x;std::cin >> x >> d[--x];return x;};int x, n = 0;std::cin >> x >> d[--x];std::vector<char> vis(N);std::vector<std::vector<int>> adj(N);auto dfs = [&](auto &&self, int u)->void{n = std::max(n, u);if(adj[u].size() < d[u]) {int v = move(adj[u].size() + 1);adj[u].push_back(v);self(self, v);} else{vis[u] = 1;for(int i = 0; i < adj[u].size(); i += 1) {int v = adj[u][i];if(vis[v]) continue;move(i + 1);self(self, v);for(int j = 0; j < adj[v].size(); j += 1) {if(adj[v][j] == u) {move(j + 1);break;}}}}};dfs(dfs, 0);std::cout << "! ";for(int i = 0; i < n; i += 1) {for(auto &v : adj[i]) {if(i > v) {continue;}std::cout << i + 1 << " " << v + 1 << " ";}}std::cout << std::endl;std::string s;std::cin >> s;}
int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t;std::cin >> t;while (t--) {solve();}return 0;
}