关于并查集

目录

概念

主要操作

初始化

合并

查询

P3367 【模板】并查集 

237. 程序自动分析

思路

代码实现 

238. 银河英雄传说(带权并查集)

思路 

代码实现 

240. 食物链(带权并查集)

思路

代码实现 


概念

并查集是一种非常精巧而实用的数据结构,它主要用于处理不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的Kruskal算法和求最近公共祖先(LCA)等。

主要操作

1.初始化init

2.合并unionn

3.查询find

初始化

用fa[ ]数组来存储每个元素的父节点,一开始每个元素的父节点是它本身。

void init(int n)
{
	for(int i=1;i<=n;i++)
	fa[i]=i;
}

合并

每个元素的父节点一开始都是它本身,我们unionn(4,3),fa[4]=4,fa[3]=3,则最后fa[4]=3,也就是4的父节点是3,以此类推根节点的父节点就是它本身。

void unionn(int i,int j)
{
	int i_fa=find(i);//找到i的祖先
	int j_fa=find(j);//找到j的祖先
	fa[i_fa]=j_fa;//将i的祖先指向j的祖先
}

查询

利用路径压缩,将每个元素都压缩到同一个父节点下,如果x的父节点是x,则x是根节点。

int find(int x)
{
	if(x==fa[x])
	return x;
	else
	{
		fa[x]=find(fa[x]);
		return fa[x];
	}
}

P3367 【模板】并查集 

P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一个模板题,将给出的元素合并之后,判断询问的两个元素是否在同一个几个内就OK。 

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int fa[N];
void init(int n)
{
	for(int i=1;i<=n;i++)
	fa[i]=i;
}
int find(int x)
{
	if(x==fa[x])
	return x;
	else
	{
		fa[x]=find(fa[x]);
		return fa[x];
	}
}
void unionn(int i,int j)
{
	int i_fa=find(i);
	int j_fa=find(j);
	fa[i_fa]=j_fa;
}
int main()
{
	int n,m,x,y,q;
	cin>>n>>m;
	init(n);
	while(m--)
	{
		cin>>q>>x>>y;
    	if(q==1)
    	{
    		unionn(x,y);
		}
		else
		{
			if(find(x)==find(y))
			cout<<"Y"<<endl;
			else
			cout<<"N"<<endl;
		}
	}
	return 0;
}

237. 程序自动分析

237. 程序自动分析 - AcWing题库

题目大意就是给出几个约束条件看是否满足这些约束条件。

例如,一个问题中的约束条件为:x1 = x2 ,x2 = x3 ,x3 = x4 ,x1 ≠ x 4 ,这些约束条件显然是不可能同时被满足的,因此不成立。

思路

1.该题需要合并的数非常大(10^9),但是只用到了10^5个数,这时我们要用到离散化处理。

2.将相等的两个数合并,不相等的两个数要判断这两个数是否相等也就是被合并,如果矛盾,则不符合。

离散化就是不考虑数字的绝对大小,只考虑相对大小,将每个数按照排序之后的下标进行存放。 

代码实现 

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+9;
int fa[N],id;
unordered_map<int,int>f;
struct s
{
	int x,y,e;
}a[N];
int lisan(int x)
{
	if(f.count(x)) return f[x];
	return f[x]=id++;
}
void init(int n)
{
	for(int i=1;i<=n;i++)
	fa[i]=i;
}
int find(int x)
{
	if(x==fa[x])
	return x;
	else
	{
		fa[x]=find(fa[x]);
		return fa[x];
	}
}
void unionn(int i,int j)
{
	int i_fa=find(i);
	int j_fa=find(j);
	fa[i_fa]=j_fa;
}
signed main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		cin>>n;
		f.clear();
		init(N);
		id=1;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i].x>>a[i].y>>a[i].e;
			a[i].x=lisan(a[i].x);
			a[i].y=lisan(a[i].y);
			if(a[i].e)
			{
				unionn(a[i].x,a[i].y);
			}
		}
		int f=0;
		for(int i=1;i<=n;i++)
		{
			if(!a[i].e)
			{
				if(find(a[i].x)==find(a[i].y))
				{
					cout<<"NO"<<endl;f=1;
					break;	
				} 
			}
		}
		if(!f) cout<<"YES"<<endl;
	}
	return 0;
}

238. 银河英雄传说(带权并查集)

238. 银河英雄传说 - AcWing题库

思路 

1.将 a 列排头连接到 b 列排尾

2.计算a,b 之间的战舰数

那么我们需要做的就是如何将 a,b列想连接起来,如果将a接到b列,a列排头到根节点的距离就是b列排头的子节点个数,同时b列的根节点的子节点的个数要加上a列排头的子节点的个数进行更新。

如图所示,2到1的距离就是1的子节点数(包含本身),1的子节点数也要更新。单看a列时,将4,3合并之后,将4与2合并,那么4到根节点(2)的距离就是再加上3到2的距离。

代码实现 

#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int N=5e5+9;
int fa[N],d[N],s[N]; //s[i]代表i节点有几个子节点
void init(int n)
{
	for(int i=1;i<=n;i++)
    {
    	fa[i]=i;   
    	s[i]=1; //刚开始是它本身
	}
}
int find(int x)
{
	if(x!=fa[x])
	{
		int t=find(fa[x]); 
		d[x]+=d[fa[x]]; //我们是直接将一列的排头接到另一列的排尾,中间的距离要一次更新
		fa[x]=t;
	}
	return fa[x];
}
int main()
{
	int t;
	cin>>t;
	init(300000);
    while(t--)
    {
    	char ch;
    	int a,b;
    	cin>>ch>>a>>b;
    	int x=find(a);
    	int y=find(b);
		if(ch=='M')
    	{
    		if(x!=y)
    		{
    			fa[x]=y;
    			d[x]=s[y]; //x为a列的排头接到b列的排尾之后,到根节点的距离就是b列排头的子节点数
                s[y]+=s[x];//更新b列排头的子节点数
			}
		}
		else
		{
			if(x!=y)
			cout<<"-1"<<endl;
			else
			cout<<max(0,abs(d[a]-d[b])-1)<<endl;
		}
	}
	return 0;
}

240. 食物链(带权并查集)

240. 食物链 - AcWing题库

思路

1.x,y存在x,y是同一类,x吃y,y吃x三种关系,我们分别用0,1,2来表示

2.如果x,y已经存在某种关系,就判断与当前所给关系是否矛盾

我们用d[x]来表示权重,该权重只有0,1,2三种情况。如果x,y在同一个集合内,那么就判断x,y到根节点的权重是否相等,相等就是同一类。

如果x,y不在同一个集合内,那么我们要进行合并。因为捕食关系是一个有向图,所以防止减d[y]时出现负数,我们要进行取模,因为只存在3种情况所以对3取模,如果存在n种情况,就对n取模。

代码实现 

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+9;
int fa[N],d[N];
int mod(int a,int b)
{
	return (a%b+b)%b;//防止负数的情况产生 
}
int find(int x)
{
	if(x!=fa[x])  
	{
		int t=find(fa[x]);
		d[x]=mod(d[x]+d[fa[x]],3);
		fa[x]=t;
	}
	return fa[x]; 
}
int merge(int x,int y,int t)
{
	int fx=find(x);
	int fy=find(y);
	d[fx]=mod(t+d[y]-d[x],3);
	fa[fx]=fy;
}
int main()
{
	int n,k,ans=0;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	fa[i]=i;
	while(k--)
	{
		int D,x,y;
		cin>>D>>x>>y;
		if(x>n||y>n)
		{
			ans++;
			continue;
		}
		int fx=find(x);int fy=find(y);
		if(D==1)
		{ //x,y是同类 
			if(fx==fy) //已经合并了,根节点相同 
			{ //到根节点的距离不相等说明不是同一类,相矛盾 
				if(mod(d[x],3)!=mod(d[y],3))	
				ans++; 
			} 
			else
			{
               //进行合并
			   merge(x,y,0);				
			}
		}
		else
		{ //x吃y 
		    if(x==y) //没有自己吃自己
			{
				ans++;
				continue;	
			}	
			else
			{ //x,y在一个集合内,原来已经存在捕食关系,判断与原来的捕食关系是否矛盾 
				if(fx==fy)
				{
					int t=mod(d[x]-d[y],3);//x与y之间的关系是什么
					if(t!=1) ans++; //原来的关系不是x吃y,相矛盾 
				}
				else
				{
					merge(x,y,1);	
				} 
			}
		}
	}
	cout<<ans<<endl; 
	return 0;	
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值