1. 并查集基本介绍与实现

并查集(Disjoint-Set)是一种可以动态维护若干个不重合的集合,并支持合并和查询的数据结构。即有两种基本操作:

  • Get :查询一个元素属于哪一个集合
  • Merge :把两个集合合并成一个大集合
2021-06-26 20.24.51

为了防止插入退化,本博客使用的方法是:路径压缩(在find函数中利用传参赋值的方式)

下面是并查集的实现方式

int p[N];

void init() {
    for(int i=0;i<N;i++) p[i] = i;
}

// 压缩后,时间复杂度可以认为是O(1)
int find(int x) {
    // 此处进行了路径压缩
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void merge(int x, int y) {
    int p1 = find(x);
    int p2 = find(y);
  	// 如果两者不等, 说明不在同一个集合,将两者进行合并操作
    if (p1 != p2) p[p1] = p2;
}

2. 一些基本使用

【例题】程序自动分析

在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。

考虑一个约束满足问题的简化版本:假设 x1,x2,x3,x1,x2,x3,…代表程序中出现的变量,给定 n 个形如 xi=xjxi=xj 或 xixjx_i≠x_j 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。

例如,一个问题中的约束条件为:x1=x2x2=x3x3=x4x1x4x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。

现在给出一些约束满足问题,请分别对它们进行判定。

输入格式

输入文件的第 1 行包含 1 个正整数 t,表示需要判定的问题个数,注意这些问题之间是相互独立的。

对于每个问题,包含若干行:

第 1 行包含 1 个正整数 n,表示该问题中需要被满足的约束条件个数。

接下来 n 行,每行包括 3 个整数 i,j,e,描述 1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 e=1e=1,则该约束条件为 xi=xjx_i=x_j;若 e=0e=0,则该约束条件为 xixjx_i≠x_j

输出格式

输出文件包括 t 行。

输出文件的第 k 行输出一个字符串 YES 或者 NOYES 表示输入中的第 kk 个问题判定为可以被满足,NO 表示不可被满足。

数据范围

$1≤n≤10^5 $
1i,j1091≤i,j≤10^9

输入样例:

2
2
1 2 1
1 2 0
2
1 2 1
2 1 1

输出样例:

NO
YES

实现代码如下

// 本题要进行离散化处理,此处的离散方式是:排序 -> 去重 -> 二分查找
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int p[2 * N], nums[N * 2], op[N][3], len;

int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

int getIdx(int x) {
    return lower_bound(nums, nums + len, x) - nums;
}

int main() {
    int T, n;
    cin >> T;
    while (T--) {
        cin >> n;
      	// 初始化操作
        for (int i = 0; i < 2 * N; i++) p[i] = i;
        int a, b, c;
        int cnt = -1;
        for (int i = 0; i < n; i++) {
            scanf("%d%d%d", &op[i][0], &op[i][1], &op[i][2]);
            nums[++cnt] = op[i][0], nums[++cnt] = op[i][1];
        }
        // 离散化
        sort(nums, nums + cnt + 1);
        len = unique(nums, nums + cnt + 1) - nums;
      	// 对于相等的情况,先处理,进行合并
        for (int i = 0; i < n; i++) {
            if (op[i][2]) {
                int p1 = find(getIdx(op[i][0]));
                int p2 = find(getIdx(op[i][1]));
                if (p1 != p2) p[p1] = p2;
            }
        }
      	// 对于不相等的情况,如果发现和之前矛盾吗,则输出NO
        bool flag = false;
        for (int i = 0; i < n; i++) {
            if (!op[i][2]) {
                int p1 = find(getIdx(op[i][0]));
                int p2 = find(getIdx(op[i][1]));
                if (p1 == p2) {
                    puts("NO"), flag = true;
                    break;
                }
            }

        }
        if (!flag) puts("YES");
    }
    return 0;
}