题目
给定两个不同的分别包含\(n\)个点的森林,每次可以同时在两个森林加一条相同的边,加边过程中不能出现环。问最多能加多少边,输出任一方案。\(n\le 10^5\)
题解
在submission上看到一个很吊的做法。
先确定一个根\(rt\),比如1号结点,然后遍历每个点\(u\),有三种情况:
- \(u\)在两个森林中都和\(rt\)连了,那么不用再连;
- \(u\)在两个森林中都不与\(rt\)相连,那么连上,添加答案\((u,rt)\)
- \(u\)在其中一个森林和\(rt\)相连,另一个不连,那么先插入一个栈保存起来。
需要两个并查集和两个栈,上述操作完成后,两栈内结点再互相连接并加入答案。
这个方法是正确的,因为第一次遍历操作完后,左边的森林中剩余的没和\(rt\)相连结点和右边的同样的结点交集为空。问题最优结果是其中一个森林变成树。这个算法可以保证最终一定有个森林最后会变成一个树且都是合法的。
还有各种方法,这个方法虽然简单,但是确实难想到。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 3e5 + 10;
const double eps = 1e-5;
int fa1[N];
int fa2[N];
int find(int x, int fa[]) {
if(x == fa[x]) return fa[x];
return fa[x] = find(fa[x], fa);
}
void merge(int u, int v, int fa[]) {
int fu = find(u, fa), fv = find(v, fa);
if(fu != fv) fa[fu] = fv;
}
typedef pair<int, int> PII;
vector<PII> ans;
stack<int> v1, v2;
int main() {
IOS;
int n, m1, m2;
cin >> n >> m1 >> m2;
for(int i = 1; i <= n; i++) {
fa1[i] = fa2[i] = i;
}
for(int i = 0; i < m1; i++) {
int u, v;
cin >> u >> v;
merge(u, v, fa1);
}
for(int i = 0; i < m2; i++) {
int u, v;
cin >> u >> v;
merge(u, v, fa2);
}
int rt = 1;
for(int i = 1; i <= n; i++) {
if((find(i, fa1) != find(rt, fa1)) && (find(i, fa2) != find(rt, fa2))) {
ans.push_back({rt, i});
merge(i, rt, fa1);
merge(i, rt, fa2);
}
}
for(int i = 1; i <= n; i++) {
if((find(i, fa1) != find(rt, fa1)) && (find(i, fa2) == find(rt, fa2))) v1.push(i);
if((find(i, fa1) == find(rt, fa1)) && (find(i, fa2) != find(rt, fa2))) v2.push(i);
}
while(!v1.empty() && !v2.empty()) {
ans.push_back({v1.top(), v2.top()});
merge(v1.top(), v2.top(), fa1);
merge(v1.top(), v2.top(), fa2);
while(!v1.empty() && (find(v1.top(), fa1) == find(rt, fa1))) v1.pop();
while(!v2.empty() && (find(v2.top(), fa2) == find(rt, fa2))) v2.pop();
}
cout << ans.size() << endl;
for(auto res : ans) cout << res.first << " " << res.second << endl;
}