并查集+题目

129 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

概念

用处(使用场景)

image.png

  1. 将两个集合合并
  2. 询问两个元素是否在一个集合中
  3. 例子:belong[x] = a
    1. 含义:x属于集合a
    2. 判断x和y是否属于一个集合if (belong[x] == belong[y]) 时间复杂度近乎O(1)

基本思想

image.png

  1. 用树(不一定是二叉树)的形式维护一个集合
  2. 每个集合的编号就是当前集合根节点的编号
  3. 集合中的每一个点都存储一下它的父节点是谁,用p[x]表示x的父节点

问题

  1. 如何判断树根?
    1. if (p[x] == x)
  2. 如何求x的集合编号:
    1. while (p[x] != x) x = p[x]; // 只要x不是树根就一直往上走,一直走到树根为止
    2. 优化:路径压缩:第一次找x的根节点的时候,将所有经过的点全部都指向根节点,加了这个优化之后,并查集的时间复杂度O(1)image.png
  3. 如何合并两个集合image.png
    1. px是结合x的编号,py是结合y的编号,只需要 p[x] = y
    2. 将x集合插到y集合里面去

题目

www.acwing.com/problem/con… image.png image.png

分析

做法:用并查集来维护信息 image.png

  1. 根节点可以看成是到自己的距离是0
  2. 距离:表示代数
    1. 比如x吃y:那么y是第0代(根节点是第0代),x是第一代
    2. z吃x:z是第二代,x是第一代
    3. k吃z:k是第三代,z是第二代
    4. 总而言之,都是递推的关系,吃第0代的就是第一代,吃第一代的就是第二代,以此类推
  3. 用距离来模3的到的数据
    1. 余1:可以吃根节点
    2. 余2:可以被根节点吃
    3. 余0:与根节点是同类
  4. 为什么第3代跟第0代是同类的呢?
    1. 因为3个一循环
  5. 并查集维护的是结点到根节点的距离

代码

#include <iostream>

using namespace std;

const int N = 50010;

int n, m;
int p[N], d[N]; // p:结点的父亲  d[x]:x到父亲结点的距离	

// 要维护一下d数组,find函数的作用,返回每个点的根节点
int find(int x)
{
    // 如果x不是树根的话
    if (p[x] != x)
    { 
        int t = find(p[x]);
        d[x] += d[p[x]];   // 更新成x到根结点的距离,即x到父节点的距离+父节点到根节点的距离
    	p[x] = t; 
    }
    return p[x];
}

int main()
{
    scanf("%d%d", &n, &m);
    
    // 将每个点初始化,d因为本来默认值就是0,所以不用初始化了
    for (int i = 1; i <= n; i++) p[i] = i; // 自己就是一个集合	
	
    int res = 0;  //假话的个数
    while (m--)
    {
        int t, x, y; // t:种类 0:同类,1:吃的关系  	 
        scanf("%d%d%d", &t, &x, &y);
        
        if (x > n || y > n) res++; // 假话
        else 
        {
            // 先将x和y的根结点找出来
            int px = find(x), py =find(y);
            if (t == 1) // x和y同类
            {
                // 在同一个并查集上,并且模3的余数不同的话,说明不是同类,是假话
                if (px == py && (d[x] - d[y]) % 3) res++; 
                else if (px != py) // 说明x和y不在一个集合里面,那么就要把他们放到一个集合里面去
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else
            {
                if (px == py && (d[x] - d[y] - 1) % 3) res++;
                else if(px != py)
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }
    }
    
    printf("%d\n", res);
}

t == 1 的前提下,将两个并查集合并之后的d[px]的距离怎么算呢?,因为t == 1(条件),所以他们两个分别模3的余数应该是一样的,下面这种是另外一种判断是否余数相同的写法,效果是差不多的image.png t == 2的前提下,将两个并查集合并之后的d[px]的距离怎么算呢? image.png