一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
概念
用处(使用场景)
- 将两个集合合并
- 询问两个元素是否在一个集合中
- 例子:belong[x] = a
- 含义:x属于集合a
- 判断x和y是否属于一个集合if (belong[x] == belong[y]) 时间复杂度近乎O(1)
基本思想
- 用树(不一定是二叉树)的形式维护一个集合
- 每个集合的编号就是当前集合根节点的编号
- 集合中的每一个点都存储一下它的父节点是谁,用p[x]表示x的父节点
问题
- 如何判断树根?
- if (p[x] == x)
- 如何求x的集合编号:
- while (p[x] != x) x = p[x]; // 只要x不是树根就一直往上走,一直走到树根为止
- 优化:路径压缩:第一次找x的根节点的时候,将所有经过的点全部都指向根节点,加了这个优化之后,并查集的时间复杂度O(1)
- 如何合并两个集合
- px是结合x的编号,py是结合y的编号,只需要 p[x] = y
- 将x集合插到y集合里面去
题目
分析
做法:用并查集来维护信息
- 根节点可以看成是到自己的距离是0
- 距离:表示代数
- 比如x吃y:那么y是第0代(根节点是第0代),x是第一代
- z吃x:z是第二代,x是第一代
- k吃z:k是第三代,z是第二代
- 总而言之,都是递推的关系,吃第0代的就是第一代,吃第一代的就是第二代,以此类推
- 用距离来模3的到的数据
- 余1:可以吃根节点
- 余2:可以被根节点吃
- 余0:与根节点是同类
- 为什么第3代跟第0代是同类的呢?
- 因为3个一循环
- 并查集维护的是结点到根节点的距离
代码
#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的余数应该是一样的,下面这种是另外一种判断是否余数相同的写法,效果是差不多的
t == 2的前提下,将两个并查集合并之后的d[px]的距离怎么算呢?