并查集入门学习

这篇博客深入介绍了并查集的概念,通过'A - How Many Tables'和'B - How Many Answers Are Wrong'两个实例展示了如何利用并查集解决实际问题。并查集主要应用于处理不相交集合的合并和查询,包括初始化、查找和合并操作。虽然掌握并查集可能具有挑战性,尤其是带权并查集,但理解其用途对于解决森林中树木数量和成员归属等问题非常关键。
摘要由CSDN通过智能技术生成

这个博客感觉对于并查集入门写的很好——并查集入门

A - How Many Tables

 题意:n个人给m个关系,每个关系给出两个数字 A,B,代表A认识B,规则: A认识B,B认识C,则ABC可以在一桌吃饭,问给出m个关系后,至少需要多少个桌子可以容纳下这n个人。

思路:这很明显就是并查集这种结构,处理不相交集合的合并和查询问题,问题就是求一个深林中有几棵树的问题。一般来说,一个并查集对应三个操作:
1、初始化( Init()函数 )
2、查找函数( Find()函数 )
3、合并集合函数( merge()函数 )

#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long ll;
using namespace std;
ll c[1010];

int find( int num ){
    if( c[num] != num ){//剪枝,如果发现当前结点的祖宗结点不是自己了,说明进行了merge操作
        c[num] = find( c[num] );//保持更新
    }
    return c[num];
}

void merge( int a, int b ){
    c[find(a)] = find(b);//注意merge是有方向性的
}//把b结点的祖宗结点和a结点的祖宗结点连接,这样ab就完全真的连接上了

int main()
{
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    ll t, a, b, n, m;
    cin >> t;
    while( t-- ){
        cin >> n >> m;
        for (int i = 1; i <= n; i ++ ){
            c[i] = i;
        }//初始化,一般并查集 我们常常从下标 1 开始
        while( m-- ){
            cin >> a >> b;
            merge( a, b );//合并函数
        }
        ll cnt = 0;
        for( int i = 1; i < n + 1; i++ ){
            if( c[i] == i ){//当把连接建立完成后,就找看有多少个祖宗结点,就有多少棵树,
            //要理解这段代码的含义
                ++cnt;
            }
        }
        cout << cnt << endl;
    }
    return 0;
}

B - How Many Answers Are Wrong

 题意:输入一段长度为n的序列,有q个信息,每个信息给出三个数据,从L——R(含)(数组下标从1开始)的和,问q个信息中有多少是假的,信息依次给出,判断依据是由上面信息来判断下面信息,即是说第一条信息一点是对的,因为没有其他依据来反驳它,这一点很重要。

思路:这道题我们以左端点为祖宗结点,如果两个集合的左端点相同那么就可以根据前缀和来检查是否符合条件,同时计数,如果两个集合的祖宗节点不一样,那就合并。合并过程其他很普通除了,计算sum,这里可以看看此题别人的题解,还要注意方向——哪个是祖宗节点,还要注意的一点为了方便合并,我们建立 ( ]区间

#include <iostream>
#include <cstring>
#include <algorithm>
typedef long long ll;
const int N = 2e5 + 10;
using namespace std;
int n, m, p[N], sum[N], cnt;

void ini( ){
    for( int i = 0; i < n + 1; i++ ){
        p[i] = i;
    }
    memset( sum, 0, sizeof( sum ) );
}

int find( int x ){
    if(p[x] == x) return x;//减小复杂度
    int u = find(p[x]);
    sum[x] += sum[p[x]];
    return p[x] = u;
}

void merge( int a, int b, int s ){
    int fa = find(a - 1) ,fb = find(b);
    p[fb] = fa;
    sum[fb] = s + sum[a - 1] - sum[b]; //一定要理解怎么算的
}

int main()
{

    int a, b, s;
    while( cin >> n >> m ){
        ini( );
        cnt = 0;
        while( m-- ){
            cin >> a >> b >> s;
            //a -= 1;
            int aan = find( a - 1);
            int ban = find( b );
            if( aan == ban ){
                if(  sum[b] - sum[a - 1] != s ){//类似于前缀和
                    ++cnt;
                }
            }
            else{
                merge( a, b, s );
            }
        }
        cout << cnt << endl;
    }
    return 0;
}

总结:

学习了并查集感觉比较抽象,各种找祖宗节点,特别是带权并查集这些,感觉自己学的不太好。但是知道了这种数据结构的用途,就是判断一个森林中有几棵树、某个节点是否属于某棵树等,但要灵活使用还有一定的难度.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值