• ACWing 240 食物链(并查集)


    一、题目

    动物王国中有三类动物 A,B,C这三类动物的食物链构成了有趣的环形。
    A 吃 B,B 吃 C,C 吃 A。
    现有 N 个动物,以 1∼N 编号。
    每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。
    有人用两种说法对这 N 个动物所构成的食物链关系进行描述:
    第一种说法是 1 X Y,表示 X 和 Y 是同类。
    第二种说法是 2 X Y,表示 X 吃 Y。
    此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。
    当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
    当前的话与前面的某些真的话冲突,就是假话;
    当前的话中 X 或 Y 比 N 大,就是假话;
    当前的话表示 X 吃 X,就是假话。
    你的任务是根据给定的 N 和 K 句话,输出假话的总数。
    输入格式
    第一行是两个整数 N 和 K,以一个空格分隔。
    以下 K 行每行是三个正整数 D,X,Y两数之间用一个空格隔开,其中 D 表示说法的种类。
    若 D=1,则表示 X 和 Y 是同类。
    若 D=2,则表示 X 吃 Y。

    输出格式
    只有一个整数,表示假话的数目。
    数据范围
    1≤N≤500001≤N≤50000,
    0≤K≤100000

    输入样例:
    100 7
    1 101 1
    2 1 2
    2 2 3
    2 3 3
    1 1 3
    2 3 1
    1 5 5
    输出样例:
    3

    二、基本思路

    首先要想到这个题是并查集类型的题目,并查集有明显的上级与下级的关系,所以看到这题要想一想是不是并查集的题目,用并查集的话该怎么做。
    运用并查集的话,他们会直接连到父集,展示不了他们的区别,所以需要另一个数组来展示他们的区别,这时候,我们可以想到运用周期性的下标索引(或者说是与根节点之间的距离)。
    这里有三个身份:分别表示为:对三取余,余1表示可以吃根节点,余2表示被根节点吃,余0表示与根结点同类。

    代码实现

    //知道每个点到根节点的距离(本题精华:利用距离记录每个点和根节点之间的关系),就可以判断哪个吃哪个,哪个跟哪个是同类,就能够知道哪句话说的是真,哪句话是是假
    //不需要知道每个点之间的关系,只需要知道每个点和根节点之间的关系即可判断所有点之间的关系,就跟等级制度一样,皇帝下面一级是A,皇帝下面两级是B,那么A就比B高位
    //路径压缩后,每个点都直接指向根节点,这时每个点到根节点的距离不要是1(要保持和原来未路径压缩时一样)
    //可以分为三类:余1是吃根节点,余2是被根节点吃的,余0是和根节点同类的。知道这三个,再根据题目给出的A、B、C循环食物链,即可得到这三类所有动物之间的关系
    #include <iostream>
    
    using namespace std;
    
    const int N = 50010;          //根据题目给出范围,定义大一点就行
    
    int n, k;                     //n表示有n只动物,k表示有k句话
    int p[N], d[N];               //表示p[i]表示第i个的父节点,d[i]表示第i个点到父节点的距离!!注意不是到根节点哦!!
                                  //全局变量初始化默认为0
                                  //d[i]一开始是0的,一开始进来的第一句话,所有的点都是独立的,因为是独立的,后面有相应的合并操作(将两个集合合并成一个),合并操作里面有+1,所以d就开始有了初始值
                                  //(即在main函数合并ab的区间时 d[px] =d[y] + 1-d[x]; 加一了)
    
    int find(int x)    //找到一个元素的根节点
    {
        if (p[x] != x)    //如果x的父节点不是自己(根节点的特征就是自己的父节点是自己),就执行以下语句
        {                             //当查询某个节点 i 时,如果 i 的父节点不为根节点的话,就会进行递归调用,将 i 节点沿途路径上所有节点均指向根节点,即进行路径压缩
            int tmp = find(p[x]);    //将找到的p[x](p[x]表示x的父节点)的根节点返回给临时变量t,运用了递归,更新了p[x]的父节点,使p[x]直接指向根节点
            d[x] += d[p[x]];    //d[x](x到根节点的距离) = d[x](x到父节点p[x]的距离) + d[p[x]](更新后,p[x]的父节点是根节点,所以这里表示p[x]到根节点的距离;注意一定要更新后才能够这么用,未更新的话,那么d[p[x]]就单纯指的是p[x]到父节点的距离,离根节点可能还有段距离)
            p[x] = tmp;    //将临时变量t赋值给p[x],表示x的父节点是根节点,x直接指向根节点,路径压缩
        }
        return p[x];    //返回x的父节点,也就是根节点。当x的父节点是自己的时候(根节点的时候),递归结束,return每个递归层的结果
    }
    
    
    int main()
    {
        scanf("%d%d", &n, &k);
    
        for (int i = 1; i <= n; i ++ ) p[i] = i;             //初始化的时候,父节点都是自己,也就是自己指向自己
    
        int res = 0;    //res表示说错话的句子数
        while (k -- )     //逐句话进行判断,k--是先赋值再运算,所以这里k是先赋值给while去判断是否大于0,再减减,如果k=1的话,那么就可以循环一次,k>1可以循环,然后k-1=0,接着就进行循环语句
                          //而如果是--k,k=1的话,先运算再赋值,k-1=0,0赋值给while判断,不执行循环语句
        {
            int t, x, y;           //t表示x和y的关系,x和y分别是两只动物的编号
            scanf("%d%d%d", &t, &x, &y);
    
            if (x > n || y > n) res ++ ;       //根据题目给出的条件:如果超出n,就表明是假话
            else
            {
                int px = find(x), py = find(y);    //找到x和y的根节点
                if (t == 1)    //如果这句话说x和y是同类
                {
                    if (px == py && (d[x]-d[y])%3) res ++ ;    //如果px和py相等(即同一个根节点),同类的话,两个点之间的距离应该是3的倍数,因为是以3为周期,所以每3个就可以遇到一个同类。
                    //这里表示如果不是3的倍数,就是说了假话
                    else if (px != py)    //如果不是同一个根节点,就连成一个根节点
                    {
                        p[px] = py;     //px的父节点是py,连接两个集合(或者说树)
                        d[px] = d[y] - d[x];    //如果x和y是同类,则通过画图可以得到,(d[x]+?-d[y])%3 = 0,有可能是9-3,也可能是3-9,可正可负
                                                //d[x]+?-d[y] = 3u,u = ……-2,-1,0,1,2……,因为?表示的是px到py的距离,可大也可小,只要满足(d[x]+?-d[y])%3 = 0这个等式即可,所以u是多少都无所谓,取最方便的0,故? = d[y] -d[x]
                    }
                }
                else
                {
                    if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;    //如果px和py相等(即同一个根节点),除了根节点a和与根节点d同类外,就是中间距离根节点为1的b和距离根节点为2的c,b吃根,c吃b,c-1相当于上前一位,就跟b一样
                    //这里y距离根近,y吃根,x距离根远,x吃y,故d[x]-1达到y的效果
                    //如果一样,余0,则表示真话,否则,表示假话,res+1
                    else if (px != py)    //如果不是同一个根节点,是两个独立的集合
                    {
                        p[px] = py;    //px的父节点是py,连接两个集合(或者说树)
                        d[px] = d[y] + 1 - d[x];    //与上面类似可得,因为x吃y,所以是d[x]-1达到相当于y的效果,(d[x]+?-1-d[y])%3 = 0,同理得,? = d[y]+1-d[x]
                    }
                }
            }
        }
    
        printf("%d\n", res);
    
        return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    三、find的具体过程

    在这里插入图片描述

    四、距离d[]是怎么有初始值的?

    #include <iostream>
    
    using namespace std;
    
    const int N = 50010;
    
    int n, m;
    int p[N], d[N];
    
    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, &m);
    
        for (int i = 1; i <= n; i ++ ) p[i] = i;
    
        int res = 0;
        while (m--  )
        {
            int t, x, y;
            scanf("%d%d%d", &t, &x, &y);
    
            cout<<"m="<<m<<" d[x]="<<d[x]<<" d[y]="<<d[y]<<endl;
            if (x > n || y > n) res ++ ;
            else
            {
                int px = find(x), py = find(y);
                cout<<"m="<<m<<" d[x]="<<d[x]<<" d[y]="<<d[y]<<endl;
                if (t == 1)
                {
                    if (px == py && (d[x] - d[y]) % 3) res ++ ;
                    else if (px != py)
                    {
                        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;
                        cout<<"m="<<m<<" x="<<x<<" d[x]="<<d[x]<<" y="<<y<<" d[y]="<<d[y]<<" px="<<px<<" d[px]="<<d[px]<<endl;
                        d[px] = d[y] + 1 - d[x];
                        cout<<"m="<<m<<" x="<<x<<" d[x]="<<d[x]<<" y="<<y<<" d[y]="<<d[y]<<" px="<<px<<" d[px]="<<d[px]<<endl;
                    }
                }
            }
        }
    
        printf("%d\n", res);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    加了输出语句,将中间过程输出出来

    根据样例运行的结果:

    m=6 d[x]=0 d[y]=0
    m=5 d[x]=0 d[y]=0
    m=5 d[x]=0 d[y]=0
    m=5 x=1 d[x]=0 y=2 d[y]=0 px=1 d[px]=0
    m=5 x=1 d[x]=1 y=2 d[y]=0 px=1 d[px]=1
    m=4 d[x]=0 d[y]=0
    m=4 d[x]=0 d[y]=0
    m=4 x=2 d[x]=0 y=3 d[y]=0 px=2 d[px]=0
    m=4 x=2 d[x]=1 y=3 d[y]=0 px=2 d[px]=1
    m=3 d[x]=0 d[y]=0
    m=3 d[x]=0 d[y]=0
    m=2 d[x]=1 d[y]=0
    m=2 d[x]=2 d[y]=0
    m=1 d[x]=0 d[y]=2
    m=1 d[x]=0 d[y]=2

    m=0 d[x]=0 d[y]=0
    m=0 d[x]=0 d[y]=0
    3

  • 相关阅读:
    二叉树层序遍历及判断完全二叉树
    【信息融合】基于BP神经网络和DS 证据理论实现不确定性信息融合问题附matlab代码
    【C++语言】继承
    后端 | 青训营笔记
    阿里三面:MQ 消息丢失、重复、积压问题,如何解决?
    Flink学习4 - 富函数 + 数据重分区操作 + sink 操作(kafka、redis、jdbc)
    Scala学习笔记16: 注解
    RK3568开发笔记(十):开发板buildroot固件移植开发的应用Demo,启动全屏显示
    【算法教程】排列与组合的实现
    java-php-python-ssm-基于云端的小区物业智能管理系统-计算机毕业设计
  • 原文地址:https://blog.csdn.net/e2788666/article/details/125624185