并查集学习笔记
前言#
最近在刷《算法竞赛进阶指南》,写几篇博客记录一下。
什么是并查集#
并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。来自OI wiki
并查集的操作#
并查集支持两种操作
- 合并(merge):讲两个元素所在集合合并
- 查询(find):找到当前元素所在集合的根
并查集的初始化#
初始时,每个元素都位于一个单独的集合,表示为一棵只有根节点的树。方便起见,我们将根节点的父亲设为自己。
code
for(int i = 1;i <= n; i ++)
fa[i] = i;
查询#
我们需要沿着树向上移动,直至找到根节点。
code
int find(int x)
{
if(fa[x] == x) return x;
return find(fa[x]);
}
路径压缩#
可以在查询时将经过的每个节点的父节点都设为根节点,方便以后快速查询。
code
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]); // 更新路径上节点的父节点
}
合并#
将一个集合的根节点的父节点设为另一个集合的根节点即可。
code
void merge(int x, int y)
{
x = find(x), y = find(y);
fa[x] = y;
}
例题#
1.程序自动分析#
分析:板子题,离散化即可。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
unordered_map<int, int> mp;
int n, m, idx;
int a[N], b[N], op[N];
int fa[N];
int id(int x){if(!mp.count(x)) mp[x] = ++ idx;return mp[x];}
int find(int x){if(fa[x] == x) return x;return fa[x] = find(fa[x]);}
void merge(int x, int y){fa[find(x)] = find(y);}
void solve()
{
mp.clear();
idx = 0;
cin >> n;
for(int i = 1;i <= n << 1;i ++) fa[i] = i;
for(int i = 1;i <= n;i ++)
{
cin >> a[i] >> b[i] >> op[i];
if(op[i]) merge(id(a[i]), id(b[i]));
}
for(int i = 1;i <= n;i ++)
{
if(!op[i] && find(id(a[i])) == find(id(b[i])))
{
cout << "NO\n";
return;
}
}
cout << "YES\n";
}
int main() //主函数
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while(T --)
{
solve();
}
return 0;
}
2.银河英雄传说#
操作1. 按要求顺序合并两个集合。
操作2. 询问两个元素在集合内的关系。
分析:考虑并查集,但是怎么维护两个元素在集合内的关系呢?容易想到记录每个点到该节点所在集合根节点之间的战舰数量,这样查询
那怎么去维护
code
int find(int x)
{
if(fa[x] == x) return x;
int rt = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = rt;
}
那合并的时候该如何操作呢?考虑维护另外一个数组
code
void merge(int x, int y)
{
x = find(x), y = find(y);
if(x == y) return;
fa[x] = y, d[x] = siz[y];
siz[y] += siz[x];
}
这也就是常说的边带权并查集,这道题目就被我们完美的解决了。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
const int N = 3e5 + 5, INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int n;
int fa[N], siz[N], d[N];
int find(int x)
{
if(fa[x] == x) return x;
int rt = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = rt;
}
void merge(int x, int y)
{
x = find(x), y = find(y);
if(x == y) return;
fa[x] = y, d[x] = siz[y];
siz[y] += siz[x];
}
int main() //主函数
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for(int i = 1;i < N;i ++) fa[i] = i, siz[i] = 1;
while(n --)
{
char op;
int a, b;
cin >> op >> a >> b;
if(op == 'M') merge(a, b);
else
{
if(find(a) == find(b))
cout << max(abs(d[a] - d[b]) - 1, 0) << '\n';
else cout << -1 << '\n';
}
}
return 0;
}
后面两题扩展域并查集比较简单就不讲了,直接放代码了。
3.奇偶游戏#
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
const int N = 1e5 + 5, INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int n, m, res;
int idx;
bool flag = false;
unordered_map<int, int> mp;
int id(int x)
{
if(!mp.count(x)) mp[x] = ++ idx;
return mp[x];
}
int fa[N];
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
fa[find(x)] = find(y);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for(int i = 0;i < N;i ++) fa[i] = i;
for(int i = 1;i <= m;i ++)
{
int a, b;
string op;
cin >> a >> b >> op;
a = id(a - 1), b = id(b);
if(op == "even")
{
if(find(a + m * 2) == find(b)) break;
merge(a, b);
merge(a + m * 2, b + m * 2);
}
else
{
if(find(a) == find(b)) break;
merge(a + m * 2, b);
merge(a, b + m * 2);
}
res = i;
}
cout << res << '\n';
return 0;
}
4.食物链#
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long long ull;
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int n, m, res;
int fa[N];
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
fa[find(x)] = find(y);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for(int i = 1;i <= n * 3;i ++) fa[i] = i;
while(m --)
{
int a, b, op;
cin >> op >> a >> b;
if((a == b && op == 2) || a > n || b > n)
{
res ++;
continue;
}
if(op == 1)
{
if(find(a + n) == find(b) || find(a) == find(b + n))
{
res ++;
continue;
}
merge(a, b);
merge(a + n, b + n);
merge(a + n * 2, b + n * 2);
}
else
{
if(find(a + n) == find(b) || find(a) == find(b))
{
res ++;
continue;
}
merge(a, b + n);
merge(a + n, b + n * 2);
merge(a + n * 2, b);
}
}
cout << res << '\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!