进阶——细赏并查集(2)

简介: 进阶——细赏并查集(2)

剖析例题食物链


初步分析


例题中给了我们很多零散的条件,最后要让我得到一个结果。对于这种须高效地维护关系,并快速判断是否产生了矛盾,并查集就可以很好的发挥作用。对于本题,需要维护的信息有两个:


⭐是否是同类

⭐是否存在捕食关系


解决思路


对于每只动物i创建3个元素i-A、i-B、i-C,并用这总共的3*N个元素建立并查集。这个并查集维护如下信息:


■ i-X 表示 i是属于X这个物种(或者说是集合)


■ 并查集里的每一个组表示组内所有元素代表的情况都同时发生或不发生。

例如,如果i-A和j-B在同一个组里,就表示如果i属于物种A那么,j一定属于物种B。因此,对于每一条信息,只需要按照下面进行操作就可以了:


🔗 第一种,x 和 y 属于一个集合,那么合并 x-A 和 y-A, x-B 和 y-B,x-C 和 y-C。


🔗 第二种,x 吃 y,那么合并 x-A 和 y-A, x-B 和 y-B,x-C 和 y-C。


■ 需要注意的是,合并之前要先判断合并操作是否会触发矛盾,比如对于第一种而言,就要先保证x-A 和 y-B 或者 y-C在同一组里。


存在的疑问


可能有小伙伴想问,为什么无论在不在一个集合里,都要执行合并的操作了?这是为了符合路径压缩的思想,在更多的结点最后都是和根结点直接产生关系,可以实现在数据量庞大的时候,依旧可以快速的执行合并操作。


举个栗子


在军营里,一个说我比旅长小两级,另一个说,我比军长大一级,另一个又说,我不太行,我只比排长大一点。想想通过这种散乱的描述,要获得有用信息会很困难。


假如换个维护信息的方式,一个说,我比将军小四级,另一个说,我比将军小两级,另一又说,我比将军小七级,最后一个,我就是将军…


那么,很明显的可以体会出来,后一种直接把老大作为维护的根本,那么获得的信息会更有秩序,也会更快。


对于并查集而言,并查集的找到树的根和路径压缩主要就是落实上述的事情,通过不断的和老大建立直接联系,使得查询和修改变得更加容易和轻松。


代码逐步落实


初始化并查集

因为本思路通过维护的是三个不同才层次的集合来实现对题中三个物种的区分


元素x代表的是x-A


元素x + N 代表的是x -B


元素x + 2* N 代表的是 x-C


这里对于N可以理解为权重。


处于不带权重的数组中的x就是最基础的物种A


处于带了一层权重N的数组范围的则是物种B


处于带了两层权重的数组范围的则是物种C

/***********全局变量的声明**********/
const int MAX_N = 150030,MAX_K = 100010;
const int MAX_N = 150030,MAX_K = 100010;
int N,K;
int par[MAX_N];//维护父亲结点的信息
int height[MAX_N];//树的高度
/**********主函数*************/
init(N * 3);

K次循环,录入数据,处理数据

判断假话


  1. 当前的话与前面的某些真的话冲突,就是假话;
  2. 当前的话中 X 或 Y 比 N 大,就是假话;
  3. 当前的话表示 X 吃 X,就是假话。

第一个if解决的是有没有符合范围,不符合范围?假话!❌


现在进入if的都是符合范围的数据


处理情况1:判断是同类

if(same(x,y + N) || same(x,y+2*N)) ans ++; 

如果给出的操作是说x 和 y 是同类,但是通过same函数计算出,x所在的物种A和y所在的物种B(y+N 是在物种B所在的数组范围)的祖先相同,或者x所在的物种A和y所在的物种C的祖先一样。这合理吗?这不合理,假话~,对于是同一个物种的,就将它们维护起来,方便以后查询


处理情况2:判断捕食的情况


如果给出的操作是说x捕食y,但是我传入的数据中,x和y是同类


///

same(x,y)

或者x是吃y的,结果算出来祖先一样的

same(x,y+2*N)

这合理吗?这不合理,假话

 for(int i = 0; i < K;i++)
    {
        int t,x,y;
        scanf("%d %d %d",&t,&x,&y);
        //当前的话中 X 或 Y 比 N 大,就是假话;
        if(x <0 || x > N || y < 0 || y > N) 
        {
            ans ++;
            continue;
        }
    }
        if(t == 1)//处理x 和 y 是同类的情况
        {
            if(same(x,y + N) || same(x,y+2*N)) ans ++;  
            else
            {
                unite(x,y);
                unite(x+N,y+N);
                unite(x+N*2,y+N*2);
            }
        }else //处理 "x吃y的情况" 
        {
            if(same(x,y) || same(x,y+2*N)) ans ++;
            else 
            {
                unite(x,y+N);
                unite(x+N,y+2 * N);
                unite(x + 2*N , y);
            }
        }

输出结果,完美解决

printf("%d\n",ans);

参考代码二(C++版本)


仙术直通车

#include <iostream>
using namespace std;
const int  N = 50010;
int n,m;
//p数组是并查集的父结点,d数组是距离
int p[N],d[N];
//并查集的核心函数
int find(int x)
{
    //如果x不是根节点
    if(p[x] != x)
    {
        //提前存放上x的父结点通过find函数递归找到的祖宗结点的信息
        int t = find(p[x]);
        //因为d[x]存放的就是x到它原本的父节点的距离,现在再加等于x的父节点p[x]到根节点的距离,就是x到根节点的距离
        d[x] += d[p[x]];
        p[x] = t;//更新x父结点的信息,让x的祖宗结点直接当x的父结点
    }
    return p[x];
}
int main()
{
    scanf("%d%d",&n,&m);
    //初始化每一个点
    for(int i = 1;i <= n;i++) p[i]= i; 
    int res = 0;//假话的数量
    //k次询问
    while(m--)
    {
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if(x > n || y > n) res ++;
        else
        {
            //找出x  的根节点 px 和 y的根节点py
            int px = find(x) , py = find(y);
            if(t == 1) //x 和 y是同类的情况
            {
                //如果两个是在一颗树上的,但是模出来结果不一样,那就不是同类
                if(px == py && (d[x] - d[y]) % 3) res ++;
                else if (px != py) 
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
                else  // x 吃 y的情况
                {
                    //如果在同一颗树上 d[x] 比 d[y]大的时候。x可以吃y
                    if(px == py && (d[x] - d[y] - 1) % 3) res ++;
                    else if(px != py)//不在一棵树上,现在把x合并到y这颗树上
                    {
                        p[px] = py;
                        d[px] = d[y] + 1- d[x];
                    }
                }
        }
    }
    printf("%d\n",res);
    return 0;
}



相关文章
|
3月前
|
算法 C++
c++算法学习笔记 (16) 并查集
c++算法学习笔记 (16) 并查集
|
3月前
|
算法 索引
【数据结构与算法 | 基础篇】力扣704/35/34:二分查找
【数据结构与算法 | 基础篇】力扣704/35/34:二分查找
|
3月前
|
Java
JAVA数据结构刷题 -- 二叉树进阶
JAVA数据结构刷题 -- 二叉树进阶
32 0
|
3月前
|
存储 算法 数据管理
【C++入门到精通】C++入门 ——搜索二叉树(二叉树进阶)
在C++中,本文介绍了搜索二叉树(二叉搜索树,BST)的概念和基本操作,包括搜索、插入和删除。搜索操作从根节点开始,按值大小决定左右查找;插入操作找到合适位置新建节点;删除操作需考虑无子节点、单子节点和双子节点的情况。文中还提供了非递归和递归实现的C++代码示例。此外,讨论了搜索二叉树在K模型和KV模型中的应用以及性能分析,强调了保持树平衡的重要性。
36 0
|
12月前
线段树(线段树入门笔记)
线段树(线段树入门笔记)
|
算法 Java 测试技术
二分查找算法简单进阶
这里将对刷题笔记一文末提及的几道推荐二分法进阶题目进行说明介绍。一道简单题加了一定的文字修饰,一道中等题巧用二分查找,以下为刷题笔记一链接,题目链接在文末提供。
133 0
|
存储 算法
【数据结构与算法】并查集
【数据结构与算法】并查集
【数据结构与算法】并查集
进阶——细赏并查集(1)
进阶——细赏并查集(1)
144 0
进阶——细赏并查集(1)
|
算法 搜索推荐
【数据结构与算法】:交换排序之快速排序(手绘图解+LeetCode原题)
快速排序是交换排序的一种,本质上快速排序就是采用“分而治之”的策略(分治法),将问题规模减小,再而对问题分别进行处理的排序算法。
112 0
【数据结构与算法】:交换排序之快速排序(手绘图解+LeetCode原题)
08数据结构与算法刷题之【并查集】篇
08数据结构与算法刷题之【并查集】篇
08数据结构与算法刷题之【并查集】篇