定义
在一些有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直接合并吖!(血的教训)
拓展
在有些题中
需要带权并查集和拓展并查集
引入
大家先去看一下题目
可以用并查集维护日期的占用情况
首先!有一个贪心的思路:先买收益最大的商品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;
}
带权并查集
带权并查集(根搭积木很像):
对于每个点,分别记录所属链的头结点、该点到头结点的距离以及它所在集合的大小。
每次合并将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;
}
拓展并查集
对我而言,就是同时维护多个并查集
先看一道例题
本题只需要最大的影响力,所以可以根据影响力大小将每一对冲突关系降序排序,就可以保证不满足条件时的影响力最小
首先考虑如何用并查集解决问题:我们可以设置数组[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;
}
与之类似的还有
这个可以用带权并查集做 当然用拓展并查集也ok
总结
这部分内容看似代码简洁
但是我感觉一直没有理解这个本质
故不敢妄加说明误人子弟
但是希望通过练习大量有关题目
可以有豁然开朗的一天吖!