并查集:一种可以动态维护若干个不重叠的集合,并支持合并与查询的数据结构。
1.get :查询元素属于哪一个集合
2.merge:合并两集合
使用“代表元法”表示并查集:选择固定元素代表集合
使用树形结构存储每个集合,树上每一个节点都是一个元素,树根是集合的代表元素。整个并查集是一个森林,通过维护数组fa[n]记录整个森林,fa[x]保存x 的父节点,特别的,树根的fa[x]值等于自身。
合并操作:将其中一个树根直接连接到另一棵树上,即fa[root1]=root2
查询归属:递归访问父节点直到树根。
路径压缩:(常用)
在每次查询过程中,将过程中访问过的节点(即所查询元素的全部祖先)直接指向树根:优化查询效率
按秩合并:
将集合的秩(可以是树的深度or集合大小等)记录树根上,合并时将秩较小的树根作为秩较大的树根的子节点。
(将小的结构合并到大的结构中去,只增加小的结构的查询代价)
(1)朴素并查集:
int fa[N]; //存储每个点的祖宗节点
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) fa[i] = i;
//所有元素各自独立构成集合,n棵只有根节点的树
int Get(int x) // 返回x的祖宗节点
{
if (fa[x] != x) fa[x] = Get(fa[x]);//路径压缩
return fa[x];
}
// 合并a和b所在的两个集合:
void Merge(int a,int b)
{
fa[Get(a)] = Get(b);
}
(2)维护size的并查集:
int fa[N], size[N];
//fa[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
fa[i] = i; size[i] = 1;
}
// 返回x的祖宗节点
int Get(int x)
{
if (fa[x] != x) fa[x] = Get(fa[x]);
return fa[x];
}
// 合并a和b所在的两个集合:
void Merge(int a,int b)
{
size[Get(b)] += size[Get(a)];
fa[Get(a)] = Get(b);
}
(3)维护到祖宗节点距离的并查集:
int fa[N], d[N];
//fa[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
for (int i = 1; i <= n; i ++ ) // 初始化,假定节点编号是1~n
{
p[i] = i; d[i] = 0;
}
int Get(int x) // 返回x的祖宗节点
{
if (fa[x] != x)
{
int u = Get(fa[x]);
d[x] += d[fa[x]];
fa[x] = u;
}
return fa[x];
}
// 合并a和b所在的两个集合:
void Merge(int a,int b)
{
fa[Get(a)] = Get(b);
d[Get(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
}
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int fa[N],id[N],cnt;
struct node{
int x,y,flag;
}a[N];
bool cmp(node c,node d)
{
return c.flag>d.flag;
}
int getid(int k)
{
return lower_bound(id,id+cnt,k)-id;
}
int Find(int k)
{
if(fa[k]!=k) fa[k]=Find(fa[k]);
return fa[k];
}
void merge(int c,int d)
{
fa[Find(c)]=Find(d);
}
int main()
{
int t;scanf("%d",&t);
while(t--)
{
int n;cnt=0;scanf("%d",&n);
for(int i=0;i<n;++i)
{
scanf("%d%d%d", &a[i].x, &a[i].y,&a[i].flag);
id[cnt++]=a[i].x;id[cnt++]=a[i].y;
}sort(a,a+n,cmp);
sort(id,id+cnt);cnt=unique(id,id+cnt)-id;
for(int i=0;i<=cnt;++i)
{
fa[i]=i;
}int flag=0;
for(int i=0;i<n;++i)
{
if(a[i].flag==1)
{
int xx=getid(a[i].x),yy=getid(a[i].y);
merge(xx,yy);
// printf("xx:%d,yy:%d find(xx):%d,find(yy):%d\n",xx,yy,Find(xx),Find(yy));
}
else
{
int xx=getid(a[i].x),yy=getid(a[i].y);
if(Find(xx)==Find(yy)) flag=1;
//printf("xx:%d,yy:%d find(xx):%d,find(yy):%d\n",xx,yy,Find(xx),Find(yy));
}
}
if(flag==1) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
typedef long long ll;
struct node{
int p,d;
}a[N];
int fa[N];
bool cmp(node x,node y)
{
return x.p>y.p;
}
int Find(int k)
{
if(fa[k]!=k) fa[k]=Find(fa[k]);
return fa[k];
}
void merge(int x,int y)
{
fa[Find(x)]=Find(y);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
int maxday=0;
for(int i=0;i<n;++i)
{
cin>>a[i].p>>a[i].d;
maxday=max(maxday,a[i].d);
}
sort(a,a+n,cmp);
for(int i=1;i<=maxday;++i)
fa[i]=i;
ll res=0;
for(int i=0;i<n;++i)
{
if(Find(a[i].d)>0)
{
res+=a[i].p;
merge(Find(a[i].d),Find(a[i].d)-1);
}
}
cout<<res<<endl;
}
}
边带权的并查集:
238. 银河英雄传说
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e4+10;
int fa[N],d[N],Size[N];
int Find(int k)
{
if(fa[k]==k) return k;
int root=Find(fa[k]);
d[k]+=d[fa[k]];
return fa[k]=root;
}
void merge(int x,int y)
{
x=Find(x);y=Find(y);
fa[x]=y;
d[x]=Size[y];
Size[y]+=Size[x];
}
int main()
{
for(int i=1;i<=N;++i)
{
fa[i]=i;d[i]=0;Size[i]=1;
}
int t;cin>>t;
while(t--)
{
string op;int i,j;
cin>>op>>i>>j;
if(op=="M")
{
merge(i,j);
}
else if(op=="C")
{
if(Find(i)!=Find(j)) cout <<"-1"<<endl;
else cout<<abs(d[i]-d[j])-1<<endl;
}
}
}
边带权解法
#include<bits/stdc++.h>
typedef long long LL;
const int N = 1e4+10;
using namespace std;
struct node{
int l,r,flag;
}query[N];
int fa[N*2],d[N*2],a[N*2],cnt=0,n,m;
void read()
{
int n;cin>>n>>m;
for(int i=0;i<m;++i)
{
string s;
cin>>query[i].l>>query[i].r>>s;
if(s=="even") query[i].flag=0;
else query[i].flag=1;
a[++cnt]=query[i].l-1;a[++cnt]=query[i].r;
}
sort(a+1,a+cnt+1);
cnt=unique(a+1,a+cnt+1)-a-1;
for(int i=1;i<=cnt;++i)
{
fa[i]=i;
}
}
int Find(int x)
{
if(fa[x]==x) return x;
int root=Find(fa[x]);
d[x]^=d[fa[x]];
return fa[x]=root;
}
int main()
{
read();
for(int i=0;i<m;++i)
{
int x=lower_bound(a+1,a+cnt+1,query[i].l-1)-a;
int y=lower_bound(a+1,a+cnt+1,query[i].r)-a;
int p=Find(x),q=Find(y);
if(p==q)
{
if((d[x]^d[y])!=query[i].flag)
{
cout<<i<<endl;return 0;
}
}else
{
fa[p]=q;
d[p]=d[x]^d[y]^query[i].flag;
}
}
cout<<m<<endl;
}
扩展域解法:
#include<bits/stdc++.h>
typedef long long LL;
const int N = 1e4+10;
using namespace std;
int fa[N*2],a[N*2],cnt=0,n,m;
struct node{
int l,r,flag;
}query[N];
void read()
{
int n;cin>>n>>m;
for(int i=0;i<m;++i)
{
string s;
cin>>query[i].l>>query[i].r>>s;
if(s=="even") query[i].flag=0;
else query[i].flag=1;
a[++cnt]=query[i].l-1;a[++cnt]=query[i].r;
}
sort(a+1,a+cnt+1);
cnt=unique(a+1,a+cnt+1)-a-1;
for(int i=1;i<=cnt*2;++i)
{
fa[i]=i;
}
}
int Find(int x)
{
if(fa[x]==x) return x;
return fa[x]=Find(fa[x]);
}
int main()
{
read();
for(int i=0;i<m;++i)
{
int x=lower_bound(a+1,a+cnt+1,query[i].l-1)-a;
int y=lower_bound(a+1,a+cnt+1,query[i].r)-a;
int x_odd=x,x_even=x+cnt;
int y_odd=y,y_even=y+cnt;
if(query[i].flag==0)
{//printf("x:%d;y:%d Find:x_odd:%d,y_even:%d\n",x,y,Find(x_odd),Find(y_even));
if(Find(x_odd)==Find(y_even))
{
cout<<i<<endl;return 0;
}
fa[Find(x_odd)]=Find(y_odd);
fa[Find(x_even)]=Find(y_even);
}
else
{
if(Find(x_odd)==Find(y_odd))
{
cout<<i<<endl;return 0;
}
fa[Find(x_odd)]=Find(y_even);
fa[Find(x_even)]=Find(y_odd);
}
}
cout<<m<<endl;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+10;
int fa[N*3];
int Find(int x)
{
if(x==fa[x]) return x;
return fa[x]=Find(fa[x]);
}
int main()
{
int n,k;cin>>n>>k;
for(int i=1;i<=n*3;++i)
{
fa[i]=i;
}int ans=0;
for(int i=0;i<k;++i)
{
int d,x,y;
cin>>d>>x>>y;
if(x>n||y>n) ans++;
else if(d==1)
{
if(Find(x)==Find(y+n)||Find(x)==Find(y+2*n))
ans++;
else
{
fa[Find(x)]=Find(y);
fa[Find(x+n)]=Find(y+n);
fa[Find(x+2*n)]=Find(y+2*n);
}
}
else if(d==2)
{
if(x==y||Find(x)==Find(y)||Find(x)==Find(y+n))
{
ans++;
}
else
{
fa[Find(x)]=Find(y+2*n);
fa[Find(x+n)]=Find(y);
fa[Find(x+2*n)]=Find(y+n);
}
}
}
cout<<ans<<endl;
}