并查集学习笔记

 


前言#

最近在刷《算法竞赛进阶指南》,写几篇博客记录一下。

什么是并查集#

并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。来自OI wiki

并查集的操作#

并查集支持两种操作

  1. 合并(merge):讲两个元素所在集合合并
  2. 查询(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. 询问两个元素在集合内的关系。

分析:考虑并查集,但是怎么维护两个元素在集合内的关系呢?容易想到记录每个点到该节点所在集合根节点之间的战舰数量,这样查询 a,b 时只需要输出 abs(d[a]d[b])1 即可。

那怎么去维护 d 数组呢?在路径压缩的过程中,我们把路径上所有经过节点的父节点都设为根节点,只需要在中间顺便维护一下就行了。

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;
}

那合并的时候该如何操作呢?考虑维护另外一个数组 sizex 表示 x 为根的集合中元素个数,在 fa[x]=y 的时候让 d[x]+=size[y]size[y]+=siz[x]
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;
}
posted @   Svemit  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
主题色彩