并查集の进阶用法

这篇具有很好参考价值的文章主要介绍了并查集の进阶用法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

普通并查集

我们在处理问题的时候,可能会遇到一些需要维护每个元素所在的集合的问题,而并查集却恰好完美解决了这个问题。

对于普通的并查集,他支持的操作比较少,只有合并和查询,合并是指把两个集合合并成一个,而查询是询问两个元素是否在同一集合内;对于这两种操作,我们可以用一个数组 \(f\) 来存放当前点所属的集合的代表元素编号,例如,\(f[i]=3\) 表示的就是第 \(i\) 个元素属于编号为 \(3\) 的元素所在的集合,那么你可能会问了,用了这个数组,查询是好办了,我们可以直接写一个 find 函数来查询:

inline void fid(int x){return f[x]==x?x:fid(f[x]);}
//如果当前的父节点是自己就返回自己,反之返回代表元素的集合所在的代表元素编号依次直到f[x]==x;

当然我们在使用的时候发现这个 fid 如果一直往后递归会很费时间,所以我们用可以用以下的代码来优化一下:

inline void fid(int x){return f[x]==x?x:f[x]=fid(f[x]);}

这种优化也叫路径压缩,我个人的理解就是加了个记忆化。

那合并呢,咋搞?

假设我们需要把 \(x\)\(y\) 所属的两个集合合并。

法一:直接暴力把所有的点遍历一遍,把所有的属于 \(y\) 所属的集合的 \(f\) 数组给暴力修改成 \(x\) 所属的集合的代表元素编号。复杂度 \(O(n)\)

法二:只修改 \(f[y]\) 的值,让他所属的集合代表元素的编号修改为 \(x\) 所属的集合代表元素的编号,后面在遇到和 \(y\) 原先属于同一集合的元素时再更新。复杂度 \(O(1)\)

很显然第一个会 T 飞,第二个复杂度很不错但是如何来实现呢?

我们之前记录的 \(f\) 数组是有大用的,配合我们的 fid 函数使我们可以轻而易举的完成法二,当你把 \(y\) 所属的集合的代表元素编号改为 \(x\) 所属的集合的代表元素编号,这样我们在后面的时候再查到所属集合为 \(y\) 所属的集合的元素时,我们就会因为 fid 函数一步一步递归更新 \(f\) 数组的值来实现法二操作了。

但是当 \(f[y]=k\)\(f[k]=k\) 的时候怎么办,那样不就没法更新了吗?

所以我们在进行修改的时候改的其实并不是把 \(f[y]=f[x]\),而是 \(f[fid(y)]=f[fid(x)]\),直接从根源合并,这样就可以做到合并操作了。

P3367 【模板】并查集

code:

#include<bits/stdc++.h>
using namespace std;
int n,m,f[100010];
inline int fid(int x)
{
	if(f[x]==x)return x;
	else return f[x]=fid(f[x]);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  f[i]=i; 
	for(int i=1;i<=m;i++)
	{
		int op,x,y;
		cin>>op>>x>>y;
		if(op==1)
		{
			int xx=fid(x);
			int yy=fid(y);
			f[xx]=f[yy];
		}
		else if(op==2)
		{
			int xx=fid(x);
			int yy=fid(y);
			if(xx==yy)
			  cout<<"Y"<<endl;
			else cout<<"N"<<endl;
		}
	}
	return 0;
}

扩展域并查集

我也不知道是不是叫这个名字,但是是解决 \(n\) 个点有 \(m\) 对关系,把 \(n\) 个节点放入两个集合里,要求每对存在关系的两个节点不能放在同一个集合这类的问题的一个并查集。

结合一道题目来讲一下具体怎么用:

P1892 [BOI2003]团伙

一个人的朋友的朋友是朋友
一个人的敌人的敌人是朋友

把一个人 \(i\) 劈成两半,一好 \(i\) 一坏 \(i+n\),当一个人 \(j\) 和他是朋友的时候,和好的是同一个集合,合并 \(j\)\(i\);如果是敌人,就和坏的是同一集合,合并 \(j\)\(i+n\)\(j+n\)\(i\)

最后你就会发现,和 \(i\) 为敌的都和 \(i+n\) 在一个集合里,和 \(i\) 处于不同集合,这样我们也就完成了题目的要求。

如果 \(a\)\(b\) 是敌人,合并 \(n+b\)\(a\)\(n+a\)\(b\)

如果 \(c\)\(a\) 是敌人,合并 \(n+c\)\(a\)\(n+a\)\(c\)

那么 \(b\)\(c\) 就并在一起了

code:

#include<bits/stdc++.h>
using namespace std;
int n,m,f[15000],bd[15000],ans=0;
int fid(int x)
{
	if(f[x]==x)return f[x];
	else return f[x]=fid(f[x]);
}
void cun(int x,int y)
{
	int xx=fid(x);
	int yy=fid(y);
	if(xx!=yy)f[yy]=xx;	
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n*2;i++)
	f[i]=i;
	for(int i=1;i<=m;i++)
	{
		int b,c;
		char a;
		cin>>a>>b>>c;
		if(a=='F')
		cun(b,c);
		if(a=='E')
		{
			cun(b+n,c);
			cun(c+n,b);
		}
	}
	for(int i=1;i<=n;i++)
	{
	   	int t=fid(i);
	   	if(bd[t]==0)
	   	  bd[t]=1,ans++;
	}
	cout<<ans<<endl;
	return 0;
}

P2024 [NOI2001] 食物链

这个题和上面的有什么区别?

上一个题目里一个人只需要劈两半,因为 A 可以和 B 为敌,B 和 A 为敌。

但是这个不可以,A 可以吃 B,但这样 B 不能吃 A。

所以我们一个人劈三半!A 吃 B,B 吃 C,C 吃 A!

在合并的时候,我们就正常每一个集合都合并,对于 A 吃 B 之类的操作,依次标记即可。

code:

#include<bits/stdc++.h>
#define N 10001000
using namespace std;
int n,k,f[N],ans,a,b,c;
inline int fid(int x){return f[x]==x?x:f[x]=fid(f[x]);} 
signed main()
{
	cin>>n>>k;
	for(int i=1;i<=3*n;i++)f[i]=i;
	for(int i=1;i<=k;i++)
	{
		cin>>a>>b>>c;
		if(b>n||c>n){ans++;continue;}
		if(a==1)
		{
			if(fid(b+n)==fid(c)||fid(c+n)==fid(b)){ans++;continue;}
			f[fid(b)]=fid(c);
			f[fid(b+n)]=fid(c+n);
			f[fid(b+n+n)]=fid(c+n+n);
		}
		if(a==2)
		{
			if(fid(b)==fid(c)||fid(b)==fid(c+n)){ans++;continue;}
			f[fid(b+n)]=fid(c);
			f[fid(b+n+n)]=fid(c+n);
			f[fid(b)]=fid(c+n+n); 
		}
	}
	cout<<ans<<endl;
	return 0;
}

加权并查集

我记得这东西还叫带权并查集。

这个东西和普通的并查集有什么区别嘞?

他可以查询两个点之间的距离。

然后就没有别的了。。。。

我也不是很明白这个东西是怎么实现的,但是可以讲一下下面的这个题目(以后一定来填坑)

P1196 [NOI2002] 银河英雄传说

这个题目的询问不太一样:询问两个战舰之间的战舰数量。

我们开一个数组 \(f\) 来存放当前战舰到这一列尽头之间的战舰数量,然后用 \(fa\) 来表示当前的点所属的集合的代表元素的编号,其次,我们还需要一个 \(num\) 数组来存放当前集合,也就是这一列的战舰总数。

我们和普通的并查集相比,其实只有两点不同:

一是我们的 fid 函数,在路径亚索压缩的时候顺便把 \(f\) 数组也要处理一下,比如说,在路径压缩的时候我们是相当于把这个点接到更新后的点所在列的后面,所以我们要这样改:

inline int fid(int x)
{
	if(fa[x]==x)return x;//如果要是相等直接返回 
	int ff=fid(fa[x]);//更新后的代表元素编号 
	f[x]+=f[fa[x]];//累加求和 
	return fa[x]=ff;//返回新的代表元素编号 
}

第二就是合并操作的改变,同样我们除了改变 \(fa\) 以外还要修改 \(f\) 数组,所以我们这样合并:

f[xx]+=num[yy];//把xx一列接到yy一列的后面,所以直接加上num[yy] 
fa[xx]=yy;//更新fa 
num[yy]+=num[xx];//累加战舰数量 
num[xx]=0;//清零 

其实我觉得带权并查集都可以把集合给相成拍一列,因为这样的话能比较好理解和实现。

code:

#include<bits/stdc++.h>
#define int long long
#define N 1000100
using namespace std;
int n,x,y,num[N],f[N],fa[N];
inline int fid(int x)
{
	if(fa[x]==x)return x;//如果要是相等直接返回 
	int ff=fid(fa[x]);//更新后的代表元素编号 
	f[x]+=f[fa[x]];//累加求和 
	return fa[x]=ff;//返回新的代表元素编号 
}
signed main()
{
	for(int i=1;i<=30000;i++)
	  fa[i]=i,f[i]=0,num[i]=1;
	cin>>n;
	while(n--)
	{
		char c;
		cin>>c>>x>>y;
		int xx=fid(x);
		int yy=fid(y);
		if(c=='M')
		{
			f[xx]+=num[yy];//把xx一列接到yy一列的后面,所以直接加上num[yy] 
			fa[xx]=yy;//更新fa 
			num[yy]+=num[xx];//累加战舰数量 
			num[xx]=0;//清零 
		}
		else
		{
			if(xx!=yy)cout<<"-1"<<endl;
			else cout<<abs(f[x]-f[y])-1<<endl;
		}
	}
	return 0;
}

可持久化并查集

之前的并查集都弱爆了,这个才是最强并查集。

前置知识:主席树,并查集

md没看懂,先咕了文章来源地址https://www.toymoban.com/news/detail-431004.html

到了这里,关于并查集の进阶用法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 并查集笔记

    并查集是一种数据结构,主要用于处理一些不相交集合的合并问题。一般应用在连通图、最小生成树、Kruskal算法、最近公共祖先(LCA)等算法中。 用帮派例子理解并查集:在n个人中,分成了不同的帮派,每个帮派的人都互为朋友,朋友的朋友是朋友,例如1号和2号是朋友,1号和

    2024年02月16日
    浏览(20)
  • 修改数组【并查集】

    并查集就是对集合的合并和查询操作的统称。他要求参与运算的两个集合是不相交的(不含有相同的元素)。针对这两个集合可以进行的操作: 1.合并:将两个集合合并成一个集合。 2.查询:查询给定的两个元素是不是在同一个集合中。 每一个并查集都使用一个树来表示,数的

    2024年02月07日
    浏览(24)
  • 并查集

    并查集 并查集是用来判断两个元素是否属于同一个集合 即判断他们的根节点是否相同 1. 初始化 2.查询根节点 3.合并 集合变为一个长长的链时,查找变得十分麻烦,此时我们需要路径压缩 即每个人的直接根节点就是最上面的根节点 我们判断两个元素是否是同一个集合,看的

    2024年02月08日
    浏览(23)
  • 【数据结构】并查集

    并查集是简单的数据结构,学会并查集,为图打好基础。 是树状的数据结构,用于处理相交集合的合并与查询 通常用森林表示,一片森林表示一个集合 并查集一般需要完成 查找元素属于哪个集合 查看两个元素是否属于同一个集合 将两个集合归并成一个集合 集合的个数 假

    2024年02月19日
    浏览(27)
  • 相似图片分类 [华为]【并查集】

    题目描述: 小明想要处理一批图片,将相似的图片分类,他首先对图片的特征采样,得到图片之间的相似度,然后 按照以下规则判断图片是否可以归为一类: 1)相似度0表示两张图片相似; 2)如果A和B相似,B和C相似,但A和C不相似,那么认为A和C间接相似,可以把ABC归为一

    2024年04月12日
    浏览(24)
  • 并查集:推导部分和

    推导部分和 问题描述 对于一个长度为 N N N 的整数数列 A 1 , A 2 , ⋯ A N A_{1}, A_{2}, cdots A_{N} A 1 ​ , A 2 ​ , ⋯ A N ​ , 小蓝想知道下标 l l l 到 r r r 的部 分和 ∑ i = l r = A l + A l + 1 + ⋯ + A r sum_{i=l}^{r}=A_{l}+A_{l+1}+cdots+A_{r} ∑ i = l r ​ = A l ​ + A l + 1 ​ + ⋯ + A r ​ 是多少? 然而

    2024年02月11日
    浏览(25)
  • 并查集算法实现

    牛客测试链接 并查集(Disjoint Set)是一种用于处理集合合并与查询问题的数据结构。它支持两种操作:合并(Union)和查询(Find)。 合并操作将两个不相交的集合合并为一个集合,查询操作用于判断两个元素是否属于同一个集合。 本文将介绍并查集算法的实现,并给出一个

    2024年01月25日
    浏览(33)
  • 力扣--并查集547.省份数量

    思路分析: 首先定义变量 fa 用于记录并查集,以及城市数量 n 。 定义了并查集的两个函数, find 用于查找节点的根节点, togother 用于合并两个节点所在的集合。 在公共函数 findCircleNum 中,初始化并查集,然后遍历 isConnected 数组,将相连的城市进行合并。 最后使用 visited

    2024年03月26日
    浏览(28)
  • 【模板】并查集

    并查集是解决两元素是否属于同一集合,将一个集合合并另一集合的数据结构。具体来说,我们使用数字代替集合,比如集合1,集合2.使用数组f[i]维护元素i属于的集合,比如f[2] = 4表示元素2属于集合4。具体我们有以下实现功能的代码 1 初始化表示集合的数组。     表示元素

    2024年01月25日
    浏览(28)
  • 数据结构--并查集

    所有元素的全集s 将各个元素划分为若干个互不相交的子集 用一个数组S[ ]即可表示“集合”关系 集合的两个基本操作―— “并” color{red}“并” “ 并 ” 和 “查” color{red}“查” “ 查 ” Find -—“查”操作:确定一个指定元素所属集合 Union --“并”操作:将两个不想交的集

    2024年02月15日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包