概念:所谓并查集,就是把一个集合合并再进行查询。
并查集基本操作:
1、将a、b两个元素合并在一个集合
2、查询a、b是否在一个集合
那么,我们这是就要思考:如何将每个元素合并在一个集合呢?它们之间是否存在着某个标志?
很显然,标志是肯定要存在的。
首先,我们可以将几个元素组成的集合看成一棵树,每个元素就是一个节点。那么我们在这里就需要借助一下树的特性:每个点都有祖先节点。既然如此,我们便可以把标志转移到各个元素的祖先节点上:
fa[x]表示元素x的父亲节点,若想找其祖先节点,只需要一直递归下去便可
那么首先就把每个元素的父亲节点定为自己
那么并查集有以下几段优美的代码:
查找公共祖先 int getfa(int k){ if(k==fa[k]) return k; fa[k]=getfa(fa[k]); return fa[k]; }
合并 x=getfa(x),y=getfa(y); if (x!=y) fa[y]=x;
判断是否在同一个集合 bool panduan(int a,int b){ return(getfa(a)==getfa(b)); }
//好吧也许我写得不够优美
那么上几道例题:
一、亲戚
题目描述:有n个人,m个关系,k个查询。2到k行,a、b,说明a、b为亲戚(间接关系也算);k+1到最后,x、y,查询它们是否为 亲戚关系
题意分析:并查集的一道模板题。将a、b合并在一个集合,就无需考虑间接关系;那么查询时只需要判断x、y是否在同一集合便可
那么代码如下:
#include<bits/stdc++.h> using namespace std; int fa[1000001]={},n,m,p; int getfa(int k){ if(k==fa[k]) return k; fa[k]=getfa(fa[k]); return fa[k]; }//查询祖先节点 int main(){ cin>>n>>m>>p; for (int i=1;i<=n;i++) fa[i]=i; for (int i=1;i<=m;i++){ int x,y; cin>>x>>y; x=getfa(x),y=getfa(y); if (x!=y) fa[y]=x;//如果不在同一集合,便合并,使他们成为亲戚 } for (int i=1;i<=p;i++){ int x,y; cin>>x>>y; int xx=getfa(x),yy=getfa(y); if (xx==yy)cout<<"Yes"<<endl;//如果在同一个集合,说明是亲戚 else cout<<"No"<<endl;//不然择不是 } return 0; }
二、犯罪团伙
题意描述:有a、b两个人,他们要么是朋友,要么是敌人。 而且有一点是肯定的: a的朋友的朋友是a的朋友; a的敌人的敌人也是a的朋友。 两个强盗是同一伙的当且仅当他们是朋友。现在小明同学想知道有多少个犯罪同伙
题目分析:这道题我开始做的时候也是毫无思路,但仔细读了几遍题目后,就知道了一个重点——敌人的敌人就是朋友!那么这样我们就可以用一个数组f[x]存储x的敌人。如此,我们就可以把敌人的敌人合并在一个集合中,即是朋友,在一个团伙
合并完后,我们便可以查询有几个团伙
那么代码如下:
#include<bits/stdc++.h> using namespace std; int a[1000001]={},fa[1000001]={},n,m; int getfa(int k){ if (k==fa[k]) return k; fa[k]=getfa(fa[k]); return fa[k]; } int main(){ cin>>n>>m; for (int i=1;i<=n;i++) fa[i]=i; for (int i=1;i<=m;i++){ int x,y; char ch; cin>>ch; cin>>x>>y; if (ch=='F'){ int xx,yy; xx=getfa(x);yy=getfa(y); if (xx!=yy) fa[yy]=xx;//如果是朋友,直接合并 } else{ int xx,yy; if (a[x]==0)a[x]=y; if (a[y]==0)a[y]=x;//敌人的敌人是朋友,记录敌人的敌人 xx=getfa(x);yy=getfa(a[y]);//把自己的祖先和敌人的敌人的祖先找到 if (xx!=yy) fa[yy]=xx;//合并 xx=getfa(y);yy=getfa(a[x]);//同样 if (xx!=yy) fa[yy]=xx;//合并 } } bool f[100001]={}; int s=0; for (int i=1;i<=n;i++){ int x=getfa(i); if (!f[x]) s++,f[x]=1;//如果祖先没出现过,说明是新的一个集合,就加一 } cout<<s;//输出 return 0; }