考虑把每个点看成一个二元组 ((A, B)) , (A) 和 (B) 分别表示这个点在第一个和第二个森林中处在的联通块的标号。
把这些 ((A, B)) 画到坐标系上,发现有同一个纵坐标或同一个横坐标的两点无法连边。
设 (x_1(a_1, b_1)) 和 (x_2(a_2, b_2)) 且 ((a_1 eq a_2 and b_1 eq b_2)),考虑在这两个点之间连边会发生什么。
- (x_2) 的坐标变成了 (x_1) 的。
- 坐标为 ((?, b_2)) 的节点都变成了 ((?, b_1))
- 坐标为 ((a_2, ?)) 的节点都变成了 ((a_1, ?))
然后你想到将所有可以与 (1) 号节点连边的点全部连边,那么最后坐标系上的点只会在直线 (y = y_1) 和 (x = x_1) 上存在。
把剩下的点分成两类,一类是 ((?, y_1)) 另一类是 ((x_1, ?)),分别按照 (?) 排序,最后将一类的第 (k) 小和另一类的第 (k) 小的点连边,然后你会发现这时坐标轴上其中一条线就会从左往右移动,最后越来越短,且每次操作都是合法的。
时间复杂度 (mathcal O (dsu + nlog n)) 。
#include <bits/stdc++.h>
#define forn(i,s,t) for(register int i=(s); i<=(t); ++i)
#define form(i,s,t) for(register int i=(s); i>=(t); --i)
#define rep(i,s,t) for(register int i=(s); i<(t); ++i)
using namespace std;
const int N = 1e5 + 3;
struct dsu {
int fa[N];
int Fnd(int u) {return fa[u] == u ? u : fa[u] = Fnd(fa[u]);}
inline void Mrg(int u, int v) {
u = Fnd(u), v = Fnd(v);
if(u == v) return ;
fa[v] = u;
}
} M, D;
int n, m1, m2, u, v, tot, resx[N], resy[N], r1[N], r2[N], tt1, tt2;
#define x(p) M.Fnd(p)
#define y(p) D.Fnd(p)
inline bool Xcmp(int A, int B) {return y(A) < y(B);}
inline bool Ycmp(int A, int B) {return x(A) < x(B);}
int main() {
scanf("%d%d%d", &n, &m1, &m2);
forn(i,1,n) M.fa[i] = D.fa[i] = i;
forn(i,1,m1) scanf("%d%d", &u, &v), M.Mrg(u, v);
forn(i,1,m2) scanf("%d%d", &u, &v), D.Mrg(u, v);
forn(i,2,n) if(x(i) != x(1) && y(i) != y(1)) resx[++tot] = 1, resy[tot] = i, M.Mrg(1, i), D.Mrg(1, i);
forn(i,1,n) {
if(x(i) == x(1) && y(i) != y(1)) r1[++tt1] = i, D.Mrg(1, i);
if(y(i) == y(1) && x(i) != x(1)) r2[++tt2] = i, M.Mrg(1, i);
}
sort(r1 + 1, r1 + tt1 + 1, Xcmp);
sort(r2 + 1, r2 + tt2 + 1, Ycmp);
forn(i,1,min(tt1, tt2)) resx[++tot] = r1[i], resy[tot] = r2[i];
printf("%d
", tot);
forn(i,1,tot) printf("%d %d
", resx[i], resy[i]);
return 0;
}