这场区分度比较低完全就是手速场嘛...趁机上了波分。
感觉这场最有思维量的就是这道D2了(D1直接n2并查集水过去了)
从D1我们就有一种感觉,题目给我们的其实是两个森林,我们每次肯定是将森林中的两棵树连接在一起。
那么我们不妨设置1号节点所在的树为主树,让森林中其他树都尽可能连接到主树上去。
所以我们先按原图读进来,之后用并查集处理一开始的连接情况,并且父节点都尽量取小(因为1为主树)
之后我们尝试将其他不在主树上的点都连一条1 - i的边,使其与主树相连。如果能成功添加也要算做答案。
之后我们发现有些点是填加不了的,比如这个点在第一张图里已经在主树中,但是第二张图这个点不在主树中。
于是我们开两个数组记录这些点,如果在一不在二就放入v2, 在二不在一就放入v1。
为什么需要记录这些点呢?因为最后的答案必然是v1, v2的组合。
v1:在第一张图中不在主树上,但是在第二张图中在主树上
v2:在第二张图中不再主树上,但是在第一张图中在主树上
那么v1, v2进行连边就一定能让v1, v2在两张图都在主树上。
连上v1, v2后有些点就会不是可以选择的点了,于是我们需要将其弹出。之后重复这个过程即为答案。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e6 + 10; int n; int a[maxn]; int fa[maxn]; int _find(int x) { if (x == fa[x]) return x; return fa[x] = _find(fa[x]); } void _merge(int u, int v) { u = _find(u), v = _find(v); if (u == v) return ; if (u > v) swap(u, v); fa[v] = u; } int main() { int n, m1, m2; scanf("%d%d%d", &n, &m1, &m2); for (int i = 1; i <= 2*n; ++ i) { fa[i] = i; } for (int i = 1; i <= m1; ++ i) { int u, v; scanf("%d%d", &u, &v); u = _find(u), v = _find(v); _merge(u, v); } for (int i = 1; i <= m2; ++ i) { int u, v; scanf("%d%d", &u, &v); u += n, v += n; u = _find(u), v = _find(v); _merge(u, v); } stack<int> p1, p2; vector<pair<int, int> > rec; for (int i = 1; i <= 1; ++ i) { for (int j = i+1; j <= n; ++ j) { int u1 = _find(i), v1 = _find(j); int u2 = _find(i+n), v2 = _find(j+n); //cout << u1 << " " << v1 << " " << u2-n << " " << v2-n << endl; if (v1 != 1 && v2 != 1+n) { _merge(1, v1); _merge(1+n, v2); rec.push_back({1, j}); continue; } if (v1 != 1) p1.push(j); if (v2 != 1+n) p2.push(j); } } while (p1.size() && p2.size()) { //cout << p1.top() << " " << p2.top() << endl; if (_find(p1.top()) == 1 && _find(p1.top()+n) == 1+n) { p1.pop(); continue; } if (_find(p2.top()) == 1 && _find(p2.top()+n) == 1+n) { p2.pop(); continue; } rec.push_back({p1.top(), p2.top()}); _merge(p1.top(), p2.top()); _merge(p1.top()+n, p2.top()+n); p1.pop(); p2.pop(); } cout << rec.size() << endl; for (auto i : rec) { cout << i.first << " " << i.second << endl; } return 0; }