AcWing 算法基础课 并查集(持续更新中···)

并查集

处理多个元素的集合问题,主要解决元素分组问题,支持合并查询两种操作。原理是一个集合是一棵树,根节点就是所有儿子的祖宗。

模板题链接: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 bc 的距离, 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);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值