目录
概念
并查集是一种非常精巧而实用的数据结构,它主要用于处理不相交集合的合并问题。一些常见的用途有求连通子图、求最小生成树的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. 程序自动分析
题目大意就是给出几个约束条件看是否满足这些约束条件。
例如,一个问题中的约束条件为: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. 银河英雄传说(带权并查集)
思路
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. 食物链(带权并查集)
思路
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;
}