并查集作用:将众多元素按条件构造成一个集合,举个不好的例子,1,2,3,4,5都是数字而abc是字母,所以把他们分成2个集合,比较好理解的例子是百度上那个:
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
并查集的基础操作有:
①将每个元素都当成一个集合,此时每个元素的根节点都是自身
②查找每个集合的根节点【代码里的find()】
③合并相同根节点的集合
以下从图的角度理解,图中每个字母代表一个元素,箭头指向的为根节点【引用CYJB博客园图片】
初始化,每个元素(节点)指向自己:
查找:查找过程能起到路径压缩作用,见图,如果不压缩,那么要找d的根节点需要先找到c,再找到c的父节点...,路径压缩使多个节点直接指向根节点,而不需经过父节点(即父节点就是根节点)
合并:将两个根节点不同的集合合并,即让其中一个集合成为另一个集合原先根节点的新根节点
//以上图片来自网上博客
例题:杭电1213 How Many Tables |
题意:某人要办生日,来的朋友不想和不认识的人坐同一张桌子,给定朋友数和朋友的认识关系,问至少需要多少桌子(A认识B,B认识C,那么ABC就是相互认识了)
#include<cstdio>
#define MAX 1002
int f[MAX];
int find(int x)
{
if( x == f[x] ) return x;
return f[x] = find(f[x]);
}
void merge(int x,int y)
{
x = find(x);
y = find(y);
if(x != y)
f[x] = y; //将y的集合合并到x的集合
}
int main()
{
int i,n,m,t,a,b,res;
// freopen("a.txt","r",stdin);
while( ~scanf("%d",&t) )
{
while(t--)
{
res = 0;
scanf("%d%d",&n,&m);
for(i=1;i<MAX;i++) //初始化假定每个人只认识自己
f[i] = i;
while(m--)
{
scanf("%d%d",&a,&b);
merge(a,b); //输入ab,他们认识,合并在一同一集合
}
for(i=1;i<=n;i++)
{ //判断有多少个集合就是有多少群人相互认识,就有多少桌子
if( f[i] == i )
res++; //判断集合数目是根据根节点指向自己
}
printf("%d\n",res);
}
}
return 0;
}
这种递归形式的路径压缩find()有个缺点:数据大(如10W)时可能栈溢出,于是有了非递归的路径压缩find()
int find(int x)
{
if( x == f[x] ) return x;
int rt=x;
while(rt!=f[rt]) //找到根结点
rt=f[rt];
int fx;
while(x!=rt)
{
fx=f[x];
f[x]=rt;
x=fx;
}
return x;
}
也是裸题,输入的是字符串,需要map一下,注意数组大小是 2*n,n个输入有2*n个字符串
#include <iostream>
#include <algorithm>
#include <math.h>
#include <string.h>
#include <cmath>
#include <map>
using namespace std;
const int maxn = 100005;
const int maxM = 25300*502;
int root[maxn*2];
int n;
int findRoot(int cur){
if(cur == root[cur]){
return cur;
}
return root[cur] = findRoot(root[cur] );
}
int merge(int x,int y){
int rootX = findRoot(x);
int rootY = findRoot(y);
if(rootX != rootY){
root[rootX] = rootY;
}
}
int main() {
int op;
string str1,str2;
map<string,int>mp;
while (cin >> n ) {
for (int i = 0; i <= n; i++) {
root[i] = i;
}
mp.clear();
int cnt = 0;
for (int i = 0; i < n; i++) {
cin >> op >> str1 >> str2;
int idx1 ,idx2 ;
idx1 = mp[str1];idx2 = mp[str2];
if( mp[str1] == 0){
idx1 = mp[str1] = ++cnt;
}
if( mp[str2] == 0){
idx2 = mp[str2] = ++cnt;
}
if(op==1){
if( findRoot(idx1) == findRoot(idx2) ){
cout<<"yes"<<endl;
}else{
cout<<"no"<<endl;
}
}else{
merge(idx1,idx2);
// for (int i = 0; i < cnt; i++) {
// cout<<word[i]<< " " << i << " "<<findRoot(i) << endl;
// }
}
}
}
return 0;
}
当我们希望集合不仅仅表示元素间是不是同属集合,还需要表示它们的关系时,就需要另开数组
输入a,b表示ab异性,问是不是会造成矛盾,设r[]=0表示同性,r[]=1表示异性。
#include<stdio.h>
const int maxn=2015;
int f[maxn],r[maxn];
void init(int n)
{
for(int i=1;i<=n;i++)
{
f[i]=i;
r[i]=0;
}
}
int find(int x)
{
if(x==f[x]) return x;
int fx=f[x];
f[x]=find(f[x]);
r[x]=(r[fx]+r[x])%2;
return f[x];
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
{
f[fy]=fx;
r[fy]=(r[x]+r[y]+1)%2;
}
}
#define sc(x) scanf("%d",&x);
int main()
{
// freopen("1.in","r",stdin);
int t;
sc(t)
for(int ce=1;ce<=t;ce++)
{
int n,m;
sc(n);sc(m);
init(n);
int no=0;
while(m--)
{
int x,y;
sc(x) sc(y)
if(no) continue;
if(find(x)==find(y))
{ //同一集合时(r[x]+r[y])%2为0就与输入的关系1矛盾
if(r[x]==r[y])
no=1;
}
else //不同集合的合并肯定不会有问题
merge(x,y);
}
if(no)
printf("Scenario #%d:\nSuspicious bugs found!\n\n",ce);
else
printf("Scenario #%d:\nNo suspicious bugs found!\n\n",ce);
}
return 0;
}
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define eps 10^(-6)
#define Q_CIN ios::sync_with_stdio(false);
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLR( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define MOD 10009
#define debug(x) cout<<#x<<":"<<(x)<<emdl;
const int maxn=50010;
int f[maxn],r[maxn];
//r=0:同类,r=1:被父亲吃,r=2:吃父亲
int find(int x)
{
if(f[x]==x) return x;
int fx=f[x]; //原父亲(fx)
f[x]=find(f[x]); //ffx
r[x]=(r[x]+r[fx])%3;
return f[x];
}
void init(int n)
{
for(int i=1;i<=n;i++)
{
f[i]=i;
r[i]=0;
}
}
int merge(int x,int y,int d)
{
int fx=find(x);
int fy=find(y);
f[fy]=fx; //被 x 吃,所以以 x 的根为父
r[fy]=(3-r[y] + d-1 + r[x])%3;
}
#define sc(x) scanf("%d",&x);
int main()
{
// RE
int n,k,x,y,t,d;
sc(n) sc(k)
{
int cnt=0;
init(n);
while(k--)
{
sc(d) sc(x) sc(y)
if(x>n||y>n) cnt++;
else if(d==2&&x==y) cnt++;
else if(find(x)==find(y))
{
if(d==1 && r[x]!=r[y])cnt++; //不同类
if(d==2 && (r[x]+1)%3!=r[y]) cnt++;
}
else
merge(x,y,d);
}
printf("%d\n",cnt);
}
return 0;
}