Funny Game§
很有意思的一道题。
假如我们倒着进行操作,并使用
DSU维护连通块,这时可以发现一个性质。在第
x - 1次操作时,我们手中必定会存在x个连通块。此时
a[] % (x - 1)相等的点能够相互连边,这个值域的大小为x - 1。但我们有
x个连通块,所以必定可以找到一条新的边连接某两个连通块。为了防止重边,我们只允许某个连通块的祖宗连边(其实谁连都一样,只不过祖宗好找罢了,而且非祖宗节点不会考虑,就不会有重边)。
遵守这样的规则,无论如何都能构造出答案。
#include <bits/stdc++.h> struct DSU { std::vector<int> f, siz; DSU() {} DSU(int n) { init(n); } void init(int n) { f.resize(n); std::iota(f.begin(), f.end(), 0); siz.assign(n, 1); } int find(int x) { while (x != f[x]) { x = f[x] = f[f[x]]; } return x; } bool same(int x, int y) { return find(x) == find(y); } bool merge(int x, int y) { x = find(x); y = find(y); if (x == y) { return false; } siz[x] += siz[y]; f[y] = x; return true; } int size(int x) { return siz[find(x)]; } }; void solve() { int n; std::cin >> n; std::vector<int> a(n); for (int i = 0; i < n; i++) { std::cin >> a[i]; } DSU dsu(n); std::vector<int> u(n), v(n); for (int x = n - 1; x >= 1; x--) { std::vector<int> b(x, -1); for (int i = 0; i < n; i++) { if (dsu.find(i) != i) { continue; } if (b[a[i] % x] != -1) { u[x] = b[a[i] % x]; v[x] = i; dsu.merge(u[x], v[x]); break; } b[a[i] % x] = i; } } std::cout << "YES\n"; for (int i = 1; i < n; i++) { std::cout << u[i] + 1 << " " << v[i] + 1 << "\n"; } } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int t; std::cin >> t; while (t--) { solve(); } return 0; }