并查集

本文深入解析并查集数据结构的定义与实现,探讨其在处理不相交集合问题中的应用。通过具体实例,如超市购物、银河英雄传说等题目,展示了并查集在信息学竞赛中的高效解决方案。文章还介绍了带权并查集和拓展并查集的概念,以及它们在解决特定问题时的使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

摘自百度QAQ

代码

这个代码大家应该都会吧

主要是几个值得注意的点

先上代码

inline int find(int x)
{
    if(x==father[x])return x;
    return father[x]=find(father[x]);
}

嗯这个find函数没有什么好注意的 主要别忘了father数组的初始化!

​

   inline void merge(int x,int y) {
        int tx=find(x),ty=find(y);
        if(tx!=ty) f[tx]=ty;
    }

在这个里面 ,千万不要直接用x和y直接合并吖!(血的教训)

拓展

在有些题中

需要带权并查集拓展并查集

引入

UVA1316 Supermarket 

大家先去看一下题目

可以用并查集维护日期的占用情况

首先!有一个贪心的思路:先买收益最大的商品QAQ

然后 嗯......

总之一个玄学并查集记录时间 就AC了(逃

算了实在不会讲直接放代码然后切入正题

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int f[10005],n,m,ans;
struct node{
    int pi,di;
}a[10005];
bool cmp(node a,node b){
    return a.pi>b.pi;
}
inline int find(int x)
{
    if(f[x]<0)return x;
    return f[x]=find(f[x]);
}
int main()
{
    while(cin>>n)
    {
        ans=0;
        for(int i=1;i<=n;i++)
        cin>>a[i].pi>>a[i].di;
        memset(f,-1,sizeof(f));
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++)
          if(find(a[i].di)>0){
                ans+=a[i].pi;
                f[find(a[i].di)]=find(a[i].di)-1;
            }
        cout<<ans<<endl;
    }
    
    return 0;
 } 

带权并查集

P1196 [NOI2002]银河英雄传说

带权并查集(根搭积木很像):

对于每个点,分别记录所属链的头结点、该点到头结点的距离以及它所在集合的大小。

每次合并将y接在x的尾部,改变y头的权值和所属链的头结点,同时改变x的尾节点。

注意:每次查找的时候也要维护每个节点的权值。

每次查询时计算两点的权值差

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int f[30003],w[30003],size[30003],n;
inline int find(int x)
{
    if(x!=f[x]){
        int father=f[x];
        f[x]=find(f[x]);
        w[x]+=w[father];//带权并查集的find操作更新到祖先的距离
    }
    return f[x];
}
inline void hb(int xx,int yy)
{
    int x=find(xx),y=find(yy);
    f[x]=y;
    w[x]+=size[y];//带权并查集的合并操作改动所在家族的大小
    size[y]+=size[x];//带权并查集改动到祖先的距离
}
int main()
{	
    for(int i=1;i<=30002;i++)
    f[i]=i,size[i]=1;//带权并查集的初始化改动
    cin>>n;
    while(n--)
    {
        char a;int b,c;
        cin>>a>>b>>c;
        if(a=='M')hb(b,c);
        
        if(a=='C')
        {
        if(find(b)==find(c))
        cout<<abs(w[b]-w[c])-1<<endl;
        else 
        cout<<-1<<endl;			
        }
    }
    return 0;
 } 

拓展并查集

对我而言,就是同时维护多个并查集

先看一道例题

P1525 关押罪犯

本题只需要最大的影响力,所以可以根据影响力大小将每一对冲突关系降序排序,就可以保证不满足条件时的影响力最小

首先考虑如何用并查集解决问题:我们可以设置数组[1--2n],其中[a]表示处于一个监狱,[a+n]表示处于另一个监狱

从头扫描每一对关系,将a和b+n,a+n和b合并(即两人关在不同监狱)

合并之前检查a和b,a+n和b+n是否在同一集合,如果是,则最大影响力即为此对关系的影响力

#include<iostream>
#include<algorithm>
using namespace std;
int  f[40003],n,m,ans;
struct node{
	int x,y;
	int c;
}a[100005];
int find(int x){
	if(x==f[x])return x;
	return f[x]=find(f[x]);
}
bool cmp(node a,node b){
	return a.c>b.c;
}
void hb(int a,int b){
	int x=find(a),y=find(b);
	f[x]=y;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n*2;i++)f[i]=i;
	for(int i=1;i<=m;i++)
	cin>>a[i].x>>a[i].y>>a[i].c;
	sort(a+1,a+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int x=a[i].x,y=a[i].y;
		if(find(x)==find(y))
		{
			cout<<a[i].c<<endl;
			return 0;
		}
		hb(x,y+n),hb(y,x+n);
	}
	cout<<0;
	return 0;
} 

与之类似的还有

P2024 [NOI2001]食物链

这个可以用带权并查集做 当然用拓展并查集也ok

总结

这部分内容看似代码简洁

但是我感觉一直没有理解这个本质

故不敢妄加说明误人子弟

但是希望通过练习大量有关题目

可以有豁然开朗的一天吖!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值