《算法竞赛从入门到国奖》算法基础:数据结构-并查集

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》


🌸Yupureki🌸的简介:


目录

[1. 并查集前言](#1. 并查集前言)

[1.1 并查集的初始化](#1.1 并查集的初始化)

[1.2 查询操作](#1.2 查询操作)

[1.3 合并操作](#1.3 合并操作)

[1.4 判断操作](#1.4 判断操作)

[2. 【模板】并查集](#2. 【模板】并查集)

算法原理

代码实现

[3. 亲戚](#3. 亲戚)

算法原理

代码实现

[4. [USACO10OCT] Lake Counting S](#4. [USACO10OCT] Lake Counting S)

算法原理

代码实现

[5. 程序自动分析](#5. 程序自动分析)

算法原理

代码实现


1. 并查集前言

在有些问题中,我们需要维护若干个集合,并且基于这些集合要频繁执行下面的操作:

  • 查询操作:查找元素属于哪一个集合。一般会在每个集合中选取一个元素作为代表,查询的是这个集合中的代表元素;
  • 合并操作:将元素所在的集合与元素y所在的集合合并成一个集合;(注意,合并的是元素所在的集合,不是这两个元素!)
  • ·判断操作:判断元素和y是否在同一个集合

因此这就引出了一个新的数据结构:并查集。并查集的本质是一课树,这颗树的上的所有节点具有相同的某种属性,因此串在一起,表示一个集合。

1.1 并查集的初始化

我们通常用数组来表示并查集,这个数组中的下标表示节点序号 ,值表示他的父节点

初始时,其父节点就是自己

cpp 复制代码
const int N = 1e6 + 10;
int n;
int fa[N]; // 双亲表⽰法所需的数组
// 初始化并查集
void init()
{
    for(int i = 1; i <= n; i++) fa[i] = i;
}

1.2 查询操作

并查集中一颗树中有很多个节点,为了方便表示一棵树,我们通常用一颗树的根节点来代表这课树。因此如果两个节点他们的根节点相同 ,代表他们在同一个并查集

根节点的父节点等于其自身,因此遇到父节点等于其自身时即可停下

cpp 复制代码
// 查询操作
int find(int x)
{
    return fa[x] == x ? x : find(fa[x]);
}

例如在上图中,从H和G不停地向上找,直到找到根节点,此时发现他们的根节点相同,因此他们在同一个并查集中

1.3 合并操作

将x元素所在的集合与元素y所在的集合合并成一个集合:

本质其实就是让y的根节点成为x的根节点的父节点,这样顺带x和y都被连在了一起

cpp 复制代码
// 合并操作
void un(int x, int y) // 注意,函数名字不能⽤ union,因为它是 C++ 的关键字
{
    int fx = find(x);
    int fy = find(y);
    fa[fx] = fy;
}

1.4 判断操作

判断元素c和元素y是否在同一集合:

看看两者所在树的根节点是否相同。

cpp 复制代码
bool issame(int x, int y)
{
    return find(x) == find(y);
}

2. 【模板】并查集

题目链接:

P3367 【模板】并查集 - 洛谷

算法原理

并查集的模板题

代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<int> v(1);

int find(int x)
{
    return v[x] == x?x:v[x] = find(v[x]);
}

void un(int x,int y)
{
    int rootx = find(x);
    int rooty = find(y);
    v[rootx] = rooty;
}

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
        v.push_back(i);
    while(m--)
    {
        int a,b,c;cin>>a>>b>>c;
        if(a == 1)
            un(b,c);
        else
        {
            if(find(b) == find(c))
               cout<<"Y"<<endl;
            else
                cout<<"N"<<endl;
        }
    }
    return 0;
}

3. 亲戚

题目链接:

P1551 亲戚 - 洛谷

算法原理

你与亲戚的亲戚之间也是亲戚

因此把所有具有亲戚相关的节点看作一个大家庭,相当于在并查集中串在一起

代码实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<int> father(1);

int find(int x)
{
    return father[x] == x ? x : father[x] = find(father[x]);
}

void un(int x, int y)
{
    int fx = find(x);
    int fy = find(y);
    father[fx] = fy;
}

int main()
{
    int n, m, p; cin >> n >> m >> p;
    for (int i = 1; i <= n; i++)
    {
        father.push_back(i);
    }
    for (int i = 0; i < m; i++)
    {
        int a, b; cin >> a >> b;
        un(a, b);
    }
    while (p--)
    {
        int a, b; cin >> a >> b;
        if (find(a) == find(b))
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
    return 0;
}

4. [USACO10OCT] Lake Counting S

题目链接:

P1596 [USACO10OCT] Lake Counting S - 洛谷

算法原理

bfs + 并查集

在bfs中,把相邻的水地用并查集串在一起,表示为一整个大池塘

代码实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int n, m;
vector<vector<char>> v;
vector<int> father;
int dx[4] = { 1,1,1,0 };
int dy[4] = { -1,1,0,1 };

int find(int x)
{
    return father[x] == x ? x : father[x] = find(father[x]);
}

void un(int x, int y)
{
    int fx = find(x);
    int fy = find(y);
    father[fx] = fy;
}

void bfs(int x, int y)
{
    queue<pair<int, int>> q;
    q.push({ x,y });
    int pos = x * n + y;
    father[pos] = pos;
    while (q.size())
    {
        auto p = q.front();
        q.pop();
        int a = p.first;
        int b = p.second;
        for (int k = 0; k < 4; k++)
        {
            int i = a + dx[k];
            int j = b + dy[k];
            if (i < 0 || i >= n || j < 0 || j >= m || v[i][j] == '.')
                continue;
            un(i*n+j,a*n+b);
            q.push({ i,j });
        }
    }
}

int main()
{
    cin >> n >> m;
    v.resize(n);
    father.assign(n * m, -1);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            char ch; cin >> ch;
            v[i].push_back(ch);
        }
    }
    int ret = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (v[i][j] == 'W' && father[i * n + j] == -1)
            {
                ret++;
                bfs(i, j);
            }
        }
    }
    cout << ret;
    return 0;
}

5. 程序自动分析

题目链接:

P1955 [NOI2015] 程序自动分析 - 洛谷

算法原理

先利用并查集维护所有相等的信息,然后遍历所有的不相等信息,判断一下是否合法。

因为数据范围的问题,需要先对所有的数离散化处理。

代码实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;

vector<int> fa;

int find(int x)
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void un(int x, int y)
{
    int fx = find(x);
    int fy = find(y);
    fa[fx] = fy;
}

int main()
{
    int n; cin >> n;
    while (n--)
    {
        fa.clear();
        vector<long long> v;
        unordered_map<long long, int> dis;
        vector<pair<long long, long long>> m;
        vector<pair<long long, long long>> check;
        int k; cin >> k;
        while (k--)
        {
            long long i, j, e; cin >> i >> j >> e;
            v.push_back(i);
            v.push_back(j);
            if (e == 1)  
                m.push_back({ i,j });
            else
                check.push_back({ i,j });
        }
        sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()),v.end());
        for (int i = 0; i < v.size(); i++)
            dis.insert({ v[i],i });
        for (int i = 0; i < dis.size();i++)
            fa.push_back(i);
        for (auto& it : m)
        {
            un(dis[it.first], dis[it.second]);
        }
        int r = 1;
        for (auto& it : check)
        {
            if (find(dis[it.first]) == find(dis[it.second]))
            {
                cout << "NO" << endl;
                r = 0;
                break;
            }
        }
        if (r)
            cout << "YES" << endl;

    }
    return 0;
}
相关推荐
灵感__idea31 分钟前
Hello 算法:“走一步看一步”的智慧
前端·javascript·算法
lwf0061642 小时前
导数学习日记
学习·算法·机器学习
头发够用的程序员3 小时前
从滑动窗口到矩阵运算:img2col算法基本原理
人工智能·算法·yolo·性能优化·矩阵·边缘计算·jetson
武帝为此3 小时前
【数据清洗缺失值处理】
python·算法·数学建模
Halo_tjn4 小时前
Java 基于字符串相关知识点
java·开发语言·算法
念越4 小时前
算法每日一题 Day08|双指针法解决三数之和
算法·力扣
万法若空5 小时前
C++ <memory> 库全方位详解
开发语言·c++
黎阳之光5 小时前
黎阳之光透明管理:视频孪生重构智慧仓储新范式
人工智能·算法·安全·重构·数字孪生
代码中介商5 小时前
C++ 类型转换深度解析:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
青小莫5 小时前
C++之string(OJ练习)
开发语言·c++·stl