并查集----基础用法

并查集的基本用法可以简单可以分为两个:

(一)、find查找祖宗节点

(二)、merge合并集合元素

 这里我们通过做图得到最终的结果大概是这样的:

 这样查找如果数据很多,就会变得很麻烦,每一次查找祖宗节点都得从下往上依次查找,因此我们先使用一种快捷的方法--路径压缩

 这样了话得到的结果就是这样的:

这样查询就会方便快捷很多,元素的父节点就是祖宗节点。

下面就是合并集合操作

 

 以上两者就是我们并查集的基础操作,当然很多题目会把不同其他算法和并查集糅合在一起,比如离散化、位运算、贪心等等,并查集中元素表达的意义也不同。而后,并查集的变型也需要了解,加权集和扩展域,特别是前者使用比较多,下面我们通过几道例题去了解一下。

例题一、Supermarket

测试样例:
4  

50 2  10 1   20 2   30 1

7  

20 1   2 1   10 3   100 2   8 2   5 20   50 10
结果:
80
185
解题思路:这里使用了贪心的一些思想,我们优先考虑卖出利润大的商品,对每个商品在它过期之前尽量晚卖出。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int p[N], n;
struct Goods//设置一个类:p表示利润,t表示期限
{
    int p;
    int t;
    bool operator < (const Goods& x)const//意思是这里的"a < b"排序按照"goods[a].p > goods[b].p"这样排序的
    {
        return p > x.p;
    }
}goods[N];
int find(int x)//路径压缩
{
    if (x == p[x])return x;
    else
    {
        p[x] = find(p[x]);
    }
    return p[x];
}
int main()
{
    int z;
    while (cin >> z)
    {
        int maxn = 0, ans = 0;//maxn找出最长的期限,ans表示总利润
        for (int i = 0; i < z; i++)
        {
            cin >> goods[i].p >> goods[i].t;
            maxn = max(maxn, goods[i].t);
        }
        for (int i = 0; i <= maxn; i++)p[i] = i;//0~maxn都初始化,使其父节点是自己
        sort(goods, goods + z);//按照我们设置的那个顺序排序(利润从高到低)
          
        for (int i = 0; i < z; i++)
        {
            int spt = find(goods[i].t);//设一个变量存储当前期限的根节点
            if (spt == 0)continue;//如果他的根节点是0了话,意思就是延期了
            ans += goods[i].p;//如果不是0,就在spt这天卖出并且ans加上利润,由于先前已经将利润从高到低排好顺序,所以一定是最大的
            p[spt] = find(spt - 1);//合并根节点操作
        }
        cout << ans << endl;
    }
    return 0;
}

例题二、食物链。

 

测试样例:
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表示两两之间隔了一代,在判断代代之间的关系;根据题中“A吃B,B吃C,C吃A”这样的捕食闭环可以得出如该图所示:

 打个比方:图中1和自己是同类,2可以吃1,3可以吃2,根据捕食闭环可知,1可以吃3,又因为4可以吃3,所以2可以吃4,同时得出1,4是同类。

代码实现如下:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int n, m, p[N], d[N];//p[x]表示x的父节点,d[x]表示x距离根节点的距离,也就是隔了几代
int find(int x)//路径压缩
{
    if (p[x] == x)return x;
    else
    {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i <= m; i++)p[i] = i;//初始化所有动物的父节点都是自己
    int res = 0;//判断谎言数量
    while (m--)
    {
        int judge;
        cin >> judge;
        int x, y;
        cin >> x >> y;
        if (x > n || y > n)res++;//根据题意判断是否超过给定动物数量
        else
        {
            int px = find(x), py = find(y);
            if (judge == 1)//判断是不是同类
            {
                if (px == py && (d[x] - d[y]) % 3 != 0)//同一集合内不是同类关系
                {
                    res++;//Fake!
                }
                else if (px != py)//如果不在同一集合内, 就合并集合
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else//判断捕食关系
            {
                if (px == py && (d[x] - d[y] - 1) % 3 != 0)
                {
                    res++;
                }
                else if (px != py)
                {
                    p[px] = py;
                    d[px] = d[y] - d[x] + 1;
                }
            }
        }
    }
    cout << res << endl;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值