官方题解的操作好炫酷啊……
因为我们能得知的关于边的信息非常有限,所以考虑求出一条「通用」的路径,使得仅通过这些极少的路径就可以完成整张图的连通性的判断。
下面记题目中给出的两种询问分别为 $ ext{OneEdge}(u, v)$ 和 $ ext{ManyEdges}(u, S)$。
自然的,我们将目光投向这张竞赛图的哈密顿路径上。
考虑归并。比如我们现在在找 $[l, r]$ 这些点的哈密顿路径,那么可以分为两个部分 $[l, mid]$ 和 $[mid +1,r]$,递归求解这两部分的哈密顿路径。
比如 $[l, mid]$ 的哈密顿路径的起点是 $u$,$[mid+1, r]$ 的哈密顿路径的起点是 $v$,那么我们调用 $ ext{OneEdge}(u, v)$,如果边是 $u o v$ 的,那么我们就把 $u$ 放在 $[l, r]$ 的哈密顿路径的第一个,否则把 $v$ 放在第一个。
上面这么做为什么可行呢?
比如边是 $u o v$ 的,那么当把 $u$ 放在第一位后,无论下一位是 $v$ 还是 $[l, mid]$ 中 $u$ 的后继,$u$ 都有一条直接连向后面的边,所以哈密顿路径的性质不会被破坏。
第一位确定以后,我们再继续按照上面的方法去确定第二位、第三位……即可。这个过程本质上就是对两个有序数组的归并。
当然,其实完全可以不用手写这个归并过程,我们可以调用 STL 的 stable_sort,而 $ ext{OneEdge}$ 恰好就是 Compare 函数!
也就是说,我们先构造一个数组 $path_i = i$,接下来调用 stable_sort(path + 1, path + n + 1, OneEdge),得到的 $path_1 o path_2 o cdots o path_n$ 就是图中的一条哈密顿路径了。
上述调用 $ ext{OneEdge}$ 的次数约 $n log n$。
求出哈密顿路径后,显然所有 $path_i o path_j (i < j)$ 的边就没有意义了,故只需要考虑反向边。
于是我们逆序遍历 $path$ 数组,同时维护一下当前通过反向边最远能追溯到哪里,其实也就是维护一个指针 $p$,只要 $ ext{ManyEdges}(path_{now}, {path_1, path_2, cdots, path_p}) = 1$,就说明至少可以追溯到前 $p$ 位。
因为 $now$ 和 $p$ 都是单调下降的,所以上述过程是线性的,调用 $ ext{ManyEdges}$ 的次数约为 $2n$。
于是这个问题就很好地得到了解决,代码非常短。
#include <bits/stdc++.h> using namespace std; int n, t, ret; bool ans[105][105]; vector<int> path; inline bool ManyEdges(const int &x, int len) // len 表示询问的 S 是 path[0] ~ path[len] { if(len < 0) return false; cout << "2 " << x << ' ' << len + 1 << ' '; for(unsigned i = 0; i <= len; i++) cout << path[i] << ' '; cout << endl; cin >> ret; return ret; } inline bool OneEdge(const int &a, const int &b) { cout << "1 " << a << ' ' << b << endl; cin >> ret; return ret; } int main() { ios::sync_with_stdio(false); cin >> t; while(t--) { cin >> n; memset(ans, true, sizeof(ans)); path.clear(); for(int i = 0; i < n; i++) path.push_back(i); stable_sort(path.begin(), path.end(), OneEdge); int far = n - 2; for(int i = n - 1; ~i; i--) { if(far == i) { for(int j = 0; j <= i; j++) for(int k = i + 1; k < n; k++) ans[path[k]][path[j]] = false; far--; } while(ManyEdges(path[i], far)) far--; } cout << "3 "; for(int i = 0; i < n; i++, cout << endl) for(int j = 0; j < n; j++) cout << ans[i][j]; cin >> n; } }