并查集
处理多个元素的集合问题,主要解决元素分组问题,支持
合并
和查询
两种操作。原理是一个集合是一棵树,根节点就是所有儿子的祖宗。
模板题链接:AcWing 836.合并集合
初始化:
最开始每个点都是一个集合,一般把每个集合初始为它本身。一般用并查集这种数据结构的时候,最开始就要把它初始化,时间复杂度为
O
(
n
)
O(n)
O(n),模板代码如下
void init()
{
for(int i=1;i<=n;++i)
p[i]=i;
}
返回元素x的祖宗结点---find()函数,注意根节点一定有p[x]=x,同时使用了路径压缩,:
int find(int x)
{
if(p[x]!=x)//说明x不是根节点
p[x]=find(p[x]);
return p[x];
}
合并:
把
a
,
b
a,b
a,b 两个元素在的集合合并成一个集合,先找到
a
,
b
a,b
a,b 的祖宗,再把
a
a
a 的祖宗的父亲设置为
b
b
b 的祖宗,这样就完成了把
a
a
a 所在的集合合并到
b
b
b 所在的集合。反过来把
b
b
b 在的集合合并到
a
a
a 在的集合也行,都差不多。
p[find(a)]=find(b);
查询:
询问
a
,
b
a,b
a,b 两个元素是否在同一个集合里面,就是看
a
,
b
a,b
a,b 的祖宗是不是同一个
if(find(a)==find(b))
并查集相关题目:
AcWing 837.连通块中点的数量
AC部分代码:
if(str == "C")
{
cin >> a >> b;
if(find(a) == find(b))
continue;
cnt[find(b)] += cnt[find(a)];
p[find(a)] = find(b);
}
这道题的最核心的点就在这里, c n t [ f i n d ( b ) ] + = c n t [ f i n d ( a ) ] cnt[find(b)] += cnt[find(a)] cnt[find(b)]+=cnt[find(a)]一定要在 p [ f i n d ( a ) ] = f i n d ( b ) ; p[find(a)] = find(b); p[find(a)]=find(b);之前,因为如果先合并就回导致 c n t cnt cnt 加的数量不对,同时还要注意如果 a , b a,b a,b两个元素在同一个集合内就不需要再对 c n t cnt cnt 数组操作了,否则 c n t cnt cnt数组会加两次
另一道比较难的题:
AcWing 240.食物链
这道题的核心思想就是把元素合并在同一个集合,通过看与根节点的距离对
3
3
3 取模的结果来分为
3
3
3类,
d
[
x
]
d[x]
d[x] 代表
x
x
x 到父节点的距离,路径压缩就变成了到根节点的距离。
同时还有很多细节需要琢磨,比如find函数里面要先进行
t
=
f
i
n
d
(
p
[
x
]
)
t=find(p[x])
t=find(p[x]),然后
d
[
x
]
+
=
d
[
p
[
x
]
]
d[x] += d[p[x]]
d[x]+=d[p[x]],最后
p
[
x
]
=
t
p[x] = t
p[x]=t。
最先进行
t
=
f
i
n
d
(
p
[
x
]
)
t=find(p[x])
t=find(p[x])的理由是要逐步递归把
x
x
x 的距离从根节点递归下来到
x
x
x 这个点,因为每次进行的是
d
[
x
]
+
=
d
[
p
[
x
]
]
d[x] += d[p[x]]
d[x]+=d[p[x]],如下示意图(只是个示意图让自己好理解),先把
c
c
c的距离算出来,然后才能算
b
b
b 的距离的时候,
d
[
b
]
d[b]
d[b] 是
b
→
c
b→c
b→c 的距离,
d
[
p
[
b
]
]
d[p[b]]
d[p[b]] 是
c
c
c 的到根节点的距离,两者加起来就是
b
b
b 到根节点的距离。算
a
a
a 是一样的道理。这样才能保证每个点到根节点的是对的。
如何保证是逐步向下算的?这就是靠递归,每次递归出来就是往下走了一步,因为递归的出口是根节点,然后每次递归结束,就往下走一步。
第二步进行 d [ x ] + = d [ p [ x ] ] d[x] += d[p[x]] d[x]+=d[p[x]]理由是,如果先进行 t = f i n d ( p [ x ] ) t=find(p[x]) t=find(p[x]),那么 p [ x ] p[x] p[x]就变了,达不到更新 d [ x ] d[x] d[x]的目的
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
int p[MAXN], d[MAXN];//p是储存父节点,d是储存到父节点的距离
int N, K, D, X, Y;
int find(int x)
{
if(p[x] != x)
{
int t=find(p[x]);//这里要一步步回溯,然后把距离更新到到根节点的距离
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
int main()
{
scanf("%d%d", &N, &K);
for(int i = 1; i <= N; ++i)
p[i] = i;
int res = 0;
while(K--)
{
scanf("%d%d%d", &D, &X, &Y);
int px = find(X), py = find(Y);//把X和Y的父节点提前找出来
if(X > N || Y > N)
++res;
else if(D == 1)
{
if(px == py && (d[X] - d[Y]) % 3)//X和Y在同一集合,并且距离模不一样说明他俩不是同一类
++res;
else if(px != py)//他俩不在同一集合
{
p[px] = py;//把x的祖先指向y的祖先,相当于合并
d[px] = d[Y] - d[X];
}
}
else if(D == 2)
{
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",res);
}