zoukankan      html  css  js  c++  java
  • AcWing 240. 食物链

    题目传送门

    本题目是一道非常好的并查集练习题,有两种并查集的解法,分别是扩展域并查集、带权并查集,下面分别进行介绍:

    一、扩展域并查集

    1、三点认知

    • 两个同类元素的天敌集合是同一个集合,猎物集合也是同一个集合。

    • 天敌的天敌是猎物 (因为是食物环嘛)

    • 猎物的猎物是天敌 (因为是食物环嘛)

    2、扩展域思想

    (1sim n)个元素扩大为(1sim 3n)个元素,使用([1,3n])个并查集(每一个并查集中的所有元素都具有同一种特性,不同并查集中不存在相同元素)来维护(3n)元素彼此的关系。

    在这里(x)元素,(x+n)元素,(x+2n)元素三者的关系被定义为:

    (x)元素所在集合中所有(∈[1,n])的元素都是(x)元素的同类

    (x+n)元素所在集合中所有(∈[1,n])的元素都是(x)元素的天敌

    (x+2n)元素所在集合中所有(∈[1,n])的元素都是(x)元素的猎物

    (x+n)元素所在的集合中所有(∈[1,n])的元素都是(x+2n)元素的猎物

    当得到一句关于两个元素(x,y)彼此关系的描述时,如果已知目前(x,y)它们各自的猎物和天敌,以及它们是否是同类,就可以判断这句描述的真假。

    可以通过(x+n)元素来确定(x)元素目前已知的天敌,也可以通过(x+2n)元素来确定(x)元素目前的猎物,还可以通过(x)元素本身来确定(x)的同类。

    于是就能够进行语言真假的判断了!

    对于一句真话:

    (x)元素,(y)元素是同类时,将他们两者的天敌集合((x+n)元素与(y+n)元素所在集合)和猎物集合((x+2n)元素与(y+2n)元素所在集合)以及自身所在的集合分别合并。

    (x)元素是(y)元素的天敌时,将(x)元素所在集合与(y)元素的天敌集合合并,将(y)元素所在集合和(x)元素的猎物集合合并,将(x)元素的天敌集合和(y)元素的猎物集合合并

    3、实现代码

    #include<bits/stdc++.h>
    
    using namespace std;
    const int N = 150010;
    int fa[N];
    int ans;
    //带扩展域的并查集
    /**
     * 功能:寻找祖先
     * @param x
     * @return
     */
    int find(int x) {
        if (fa[x] != x) fa[x] = find(fa[x]);
        return fa[x];
    }
    
    //加入家族集合中
    void join(int x, int y) {
        int f1 = find(x), f2 = find(y);
        if (f1 != f2)fa[f1] = f2;
    }
    
    int main() {
        int n, m;
        cin >> n >> m;
        //开三个并查集,合用一个数组表示
        //对于每种生物:设x为本身,x+n为猎物,x+2*n为天敌
        for (int i = 1; i <= 3 * n; i++) fa[i] = i; //初始化
    
        while (m--) {
            int x, y, t;
            cin >> t >> x >> y;
            // 太大了,越界了,肯定是假话
            if (x > n || y > n) {
                ans++;
                continue;
            }
            //xy同类
            if (t == 1) {
                //如果y是x的天敌或猎物,为谎言
                if (find(x + n) == find(y) || find(x + 2 * n) == find(y)) {
                    ans++;
                    continue;
                }
                //x的同类和y的同类,x的猎物是y的猎物,x的天敌是y的天敌
                join(x, y);
                join(x + n, y + n);
                join(x + 2 * n, y + 2 * n);
            }
            //y是x的猎物
            if (t == 2) {
                //如果x是y的同类或猎物,为谎言
                if (find(x) == find(y) || find(x + 2 * n) == find(y)) {
                    ans++;
                    continue;
                }
                join(x, y + 2 * n);//x的同类是y的天敌
                join(x + n, y);//x的猎物是y的同类
                join(x + 2 * n, y + n); //x的天敌是y的猎物
            }
        }
        printf("%d
    ", ans);
        return 0;
    }
    

    二、带权并查集

    功能:查询祖先+修改父节点为祖先+更新节点到根的距离(通过到父节点的距离累加和)
    (d[i])的含义:第 (i) 个节点到其父节点距离。

    https://cdn.acwing.com/media/article/image/2021/01/08/2675_ab3bf90451-3.jpg

    C++ 代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 50010;
    const int M = 3;
    
    /**
     带权并查集:
     (1)不管是同类,还是吃与被吃的关系,都把它们放到同一个集合中去,关系用距离描述:
        0:同类,1:猎物, 2:天敌
     (2)通过上面的记录关系,就可以推理出任何两个节点的关系。
     */
    int n, m;
    int p[N]; //父亲是谁
    int d[N]; //i结点到父结点的距离
    int res;  //假话的个数
    
    /**
     * p是指节点i的父节点是谁
     * d是指节点i到自己父亲的距离
     * d[x]=1 : x吃根结点
     * d[x]=2 : x被根结点吃
     * d[x]=0 : x与根结点是同类
     * @param x
     * @return
     */
    int find(int x) {
        if (p[x] != x) {
            //这里将最祼并查集的代码拆开了
            int t = find(p[x]);
            //中间加了一个维护距离的代码,之所以这样做,是因为这里需要使用p[x]更新压缩路径后d[x]的值,不用破坏
            d[x] = (d[p[x]] + d[x]) % M;
            //修改p[x]
            p[x] = t;
        }
        return p[x];
    }
    
    int main() {
        //优化输入
        ios::sync_with_stdio(false);
    
        cin >> n >> m;
        //并查集初始化
        for (int i = 1; i <= n; i++) p[i] = i;
    
        //m个条件
        while (m--) {
            int t, x, y;
            cin >> t >> x >> y;
            //如果出现x,y的序号,大于最大号,那么肯定是有问题,是假话
            if (x > n || y > n) {
                res++;
                continue;
            }
            //找出祖先
            int px = find(x), py = find(y);
    
            //t==1,同类
            if (t == 1) {
                //合并并查集
                if (px != py) {
                    p[px] = py;
                    d[px] = (d[y] - d[x] + M) % M;
                } else if ((d[x] - d[y] + M) % M) res++;//不是同类,结果值++
            } else {
                //t==2,表示X吃Y
                //不在同一个家族内,描述刚录入进来的信息,肯定是真话,记录这个信息
                if (px != py) {
                    p[px] = py;
                    d[px] = (d[y] + 1 - d[x] + M) % M;
                } else if ((d[y] + 1 - d[x] + M) % M) res++;  //同一个家族,但距离并不是1,那就是错误答案
            }
        }
        //输出大吉
        printf("%d
    ", res);
        return 0;
    }
    
    

    代码解释:

  • 相关阅读:
    将博客搬至CSDN
    js进制转换
    js千分位转换
    css让div水平垂直居中
    NPM与调试工具的使用
    Windows下Node.js开发环境搭建-合适的开发环境
    Node.js开发环境介绍-调试工具
    开发环境
    模拟实现call,apply,bind方法,以及三者区别
    观察者模式
  • 原文地址:https://www.cnblogs.com/littlehb/p/15270596.html
Copyright © 2011-2022 走看看