侧边栏壁纸
博主头像
CoderKui

坐中静,舍中得,事上练

  • 累计撰写 51 篇文章
  • 累计创建 69 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

并查集

CoderKui
2021-12-19 / 0 评论 / 0 点赞 / 252 阅读 / 1,208 字 / 正在检测是否收录...

并查集

  1. 将两个集合合并
  2. 询问两个元素是否在一个集合中

并查集就是在O(1)时间复杂度下支持以上两种操作

基本原理:每个集合用一棵树来表示,树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x] 表示x的父节点

问题1:如何判断树根:if(p[x] == x)

问题2:如何求x的集合编号:while(p[x] != x) x = p[x];

问题3:如何合并两个集合:px是x的集合编号,py是y的集合编号,让p[x] = y;

并查集的精髓优化:在第一次执行查询元素所属集合后,该集合内所有元素的父节点均修改为根节点,后续查询该集合内所有元素的所属集合即可达到O(1)速度。此优化常称为路径压缩。

模版题1

一共有 nn 个数,编号是 1∼n1∼n,最开始每个数各自在一个集合中。

现在要进行 mm 个操作,操作共有两种:

  1. M a b,将编号为 aa 和 bb 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 aa 和 bb 的两个数是否在同一个集合中;

输入格式

第一行输入整数 nn 和 mm。

接下来 mm 行,每行包含一个操作指令,指令为 M a bQ a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 aa 和 bb 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes
import java.util.*;

public class Main {
    static final int N = 100010;
    static int[] p = new int[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for(int i = 1; i <= n; ++i) {
            p[i] = i;
        }
        while(m-- > 0) {
            String ops = sc.next();
            if(ops.equals("M")) {
                int a = sc.nextInt();
                int b = sc.nextInt();
                p[find(a)] = find(b);
            } else {
                int a = sc.nextInt();
                int b = sc.nextInt();
                if(find(a) == find(b)) {
                    System.out.println("Yes");
                } else {
                    System.out.println("No");
                }
            }
        }
    }
    
    // 查询元素x的祖宗节点 + 路径压缩
    public static int find(int x) {
        if(p[x] != x) {
            p[x] = find(p[x]);
        }
        return p[x];
    }
}

一般算法题中,不会考察单纯的并查集,而是需要维护一些额外信息,例如每个集合中元素的数量等

模版题2

给定一个包含 nn 个点(编号为 1∼n1∼n)的无向图,初始时图中没有边。

现在要进行 mm 个操作,操作共有三种:

  1. C a b,在点 aa 和点 bb 之间连一条边,aa 和 bb 可能相等;
  2. Q1 a b,询问点 aa 和点 bb 是否在同一个连通块中,aa 和 bb 可能相等;
  3. Q2 a,询问点 aa 所在连通块中点的数量;

输入格式

第一行输入整数 nn 和 mm。

接下来 mm 行,每行包含一个操作指令,指令为 C a bQ1 a bQ2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 aa 和 bb 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 aa 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3
import java.util.*;

public class Main {
    static final int N = 100010;
    static int[] p = new int[N];
    static int[] size = new int[N];
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for(int i = 1; i <= n; ++i) {
            p[i] = i;
            size[i] = 1;
        }
        while(m-- > 0) {
            String ops = sc.next();
            if(ops.equals("C")) {
                int a = sc.nextInt();
                int b = sc.nextInt();
                if(find(a) == find(b)) continue; // 两个元素已在一个集合,不再合并
                size[find(b)] += size[find(a)]; // 合并两个集合的size
                p[find(a)] = p[find(b)]; // 一个集合的根指向另一个集合
            } else if(ops.equals("Q1")) {
                int a = sc.nextInt();
                int b = sc.nextInt();
                if(find(a) == find(b)) {
                    System.out.println("Yes");
                } else {
                    System.out.println("No");
                }
            } else {
                int a = sc.nextInt();
                System.out.println(size[find(a)]);
            }
        }
    }
    
    // 查询元素x的祖宗节点 + 路径压缩
    public static int find(int x) {
        if(p[x] != x) {
            p[x] = find(p[x]);
        }
        return p[x];
    }
}
0

评论区