C++并查集——食物链

并查集是一种用来维护集合的简单算法。因为其实现算法很简单,经常用于面试题。

没有描述

其实现原理是构造一棵树,其中在一个集合的元素都会储存在一棵树中。如果两个集合合并,可以将集合A放在集合B的节点,达到两个集合合并后所有元素的根节点都为同一个值即可。

算法关键是构建一个函数find(),该函数用来找出该点的根节点的值。如果将A点加入到集合B中,只需将A点的根节点值修改成B集合根节点的值即可。

在这里插入图片描述
在这里插入图片描述

#include<iostream>

using namespace std;

const int N = 1e5 + 10;

int n , m;
int p[N];

int find(int x) {

    //这里十分巧妙,在寻根的过程中顺便把沿途的点的父亲值附上根节点的值
    //从而实现了路径压缩,使得每个节点直接指向该集合的根节点
    if( x != p[x] ) p[x] = find( p[x] );

    return p[x];
}

int main()
{
    scanf("%d%d" , &n , &m );

    //初始化,每个点的父亲都是自己
    for(int i = 1 ; i < n ; i++) p[i] = i;

    while(m--)
    {
        char op;
        int a , b;
        cin >> op; 
        cin >> a >> b;

        if( op == 'M' )
        {
            //将b点的父亲作为a点的父亲,间接将两个区间合并、
            //将a集合合并到b集合
            p[find(a)] = find(b);
        }
        else if( op == 'Q' )
        {
            if( find(a) == find(b) ) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }

    return 0;
}

作者:阿柴
链接:https://www.acwing.com/activity/content/code/content/596393/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

接下来是一道比较难的题:食物链

在这里插入图片描述
在这里插入图片描述

题目非常长,大概就是存在一条循环食物链,比如A 吃 B , B 吃 C , C 吃 A。我们假设用一个树来存储。

在这里插入图片描述

我们假设存在一条循环食物链,比如A 吃 B , B 吃 C , C 吃 E , E 吃 F,我们思考一下可以发现当我们已知两个动物的状态时,我们是可以推出第三个动物的状态。拿A,B,C来说,我们根据A 吃 B , B 吃 C ,可以知道C是吃A的。因为这是一条循环的食物链,知道两个动物比能推出第三个动物的状态。看回刚刚那个例子。

我们知道C是吃A的,而C也是吃E的,所以A和E是同类,也就是A和E能吃B,这个自己推推。此时,当我们用一棵树来存储的话,我们用某个点到根节点的距离来表示在该食物链中的关系。当某个动物K的距离模3为1,另外一个动物M的距离倘若模3位1,则为同类,如果M模3为0,说明M吃K;若M模3为2,则K吃M。

在这里插入图片描述

下面这张图是用来解释当两个集合合并后,两个集合元素之间的关系是什么。

在这里插入图片描述

#include<iostream>

using namespace std;

const int N = 50010;

int p[N] , d[N];
int n , m ;

int find(int x)
{
    if( x != p[x] )
    {
        //如果按表层意思,是先存一下根节点的值
        //深度分析递归过程可知,这里是开一个递归的头,并求得p[x]
        int u = find(p[x]);
        d[x] += d[ p[x] ];
        p[x] = u;
    }

    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 x , y , t;

        scanf("%d%d%d" , &t , &x , &y);

        int px = find(x) , py = find(y);

        if(x > n || y > n) res ++;
        else
        {
            //当询问是否为同级
            if( t == 1 ) 
            {
                //表示说是同级,其实不是同级关系
                if( px == py && ( d[x] - d[y] ) % 3 != 0 ) res++;
                else if( px != py ) //表示没有在同一个集合
                {
                    //将y作为x的父节点,将dx连到dy上
                    p[ px ] = py;
                    //由于连上去,所以px到py的距离是不知道的
                    //设为T,由于x和y为同级,则 (d[x] + T) % 3 = d[y] % 3
                    //所以求得T = d[y] - d[x]
                    d[ px ] = d[y] - d[x];
                }
            }
            //当询问是否可吃
            else
            {
                //如果x和y在同一集合并且y比x小1(都是模3之后)
                //应该的式子:d[x] % 3 = d[y] % 3 + 1
                if( px == py && (d[x] - d[y] - 1) % 3 ) res++;
                else if( px != py )
                {
                    p[ px ] = py;
                    //(d[x] + T) % 3 = d[y] % 3 + 1
                    //化简得:T = d[y] - d[x] + 1
                    d[ px ] = d[y] - d[x] + 1;
                }
            }
        }

    }

    cout << res << endl;

    return 0;
}

作者:阿柴
链接:https://www.acwing.com/activity/content/code/content/598066/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值