在一颗庞大的树中,我们有时候不需要将每一个点的父亲和孩子分的明明白白,有时往往只要找到他们最终的归宿就可以了,因此,我们往往需要对树进行一些操作,将每一个点的最终祖先记录下来就可以了。这就是并查集的初衷。
https://www.luogu.com.cn/problem/P3367https://www.luogu.com.cn/problem/P3367
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 𝑁,𝑀 ,表示共有 𝑁 个元素和 𝑀 个操作。
接下来 𝑀 行,每行包含三个整数 𝑍𝑖,𝑋𝑖,𝑌𝑖 。
当 𝑍𝑖=1 时,将 𝑋𝑖 与 𝑌𝑖 所在的集合合并。
当 𝑍𝑖=2 时,输出 𝑋𝑖 与 𝑌𝑖 是否在同一集合内,是的输出 Y
;否则输出 N
。
输出格式
对于每一个 𝑍𝑖=2 的操作,都有一行输出,每行包含一个大写字母,为 Y
或者 N
。
输入输出样例
输入 #1
4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4
输出 #1
N Y N Y
我们首先要对fa数组初始化,我们如何判断一个点是父亲节点呢?当然是它的父亲是它自己(好像有点问题),就是一个点的fa数组对应的数是自己本身的时候。因此我们先将每一个点的父亲初始化为自己,然后进行并查集。
并查集有两个重要的地方:
一:如何一层一层向上找到父亲节点。
二:如何修改当前fa值。
有关于如何向上找到父亲节点。我们不妨记一个findfather操作,当发现一个点的father是自己本身是就跳出向上的操作,否则继续向上找。先展示一下代码:
int findfather(int x)
{
if(fa[x] == x)
return x;
return fa[x] = findfather(fa[x]);// 此处为路径压缩
}
我这里采用了一种优化的方法,路径压缩,什么意思呢,就是在找第一轮是将查找到的位置fa值也一起向上修改,通俗点讲你在向上找祖宗时,先找到你爸爸,你爸爸再向上找到你 爷爷,那么你爷爷一定也是你爸爸的祖宗,就把爸爸一起修改,那么如果你的同辈份亲兄弟向上找时就会跳过爸爸直接找到爷爷的位置。
以上就是查,那么并呢?先展示代码:
int merge(int x, int y)
{
return fa[findfather[x]] = findfather[y];
}
这一段是十分通俗易懂的。
所以,完整代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, cmd, x, y;
int fa[N];
int findfather(int x)
{
if(fa[x] == x)
return x;
return fa[x] = findfather(fa[x]);
}
int merge(int x, int y)
{
return fa[findfather(x)] = findfather(y);
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++)
fa[i] = i;
while(m --)
{
cin >> cmd >> x >> y;
if(cmd == 1)
merge(x, y);
else
{
x = findfather(x);
y = findfather(y);
if(x == y)
cout << "Y" << endl;
else
cout << "N" << endl;
}
}
}
但事实上,并查集还有一项启发式合并,就是合并时将父节点下属元素数量少的贴到数量多的下面,但是好像比较鸡肋,就不怎么用,也就不讲。
废话:上个星期三跑了1000m,然后我是脆皮大学生,身体不适,便没有更新,本来上周末出去比赛的,直接就没去,哎>_<。