算法思想
略,较简单,给出模板
int Find(int x) {
if(x==fa[x])
return x;
return fa[x]=Find(fa[x]);
}//路径压缩查找
void Union(int x,int y) {
int _x=Find(x),_y=Find(y);
if(fa[_x]!=fa[_y])
fa[_x]=fa[_y];
}//合并
训练
HDU1232
题目大意:略
思路:统计有多少个已经连通的集合,为使得这些集合相连只需要个数-1条路
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
int N,M,fa[1212];
int Find(int x) {
if(x==fa[x])
return x;
return fa[x]=Find(fa[x]);
}
void Union(int x,int y) {
int _x=Find(x),_y=Find(y);
if(_x!=_y)
fa[_x]=fa[_y];
}
int main() {
while(~scanf("%d%d",&N,&M)&&N) {
for(int i=1; i<=N; i++)
fa[i]=i;
int x,y,ans=0;
while(M--) {
scanf("%d%d",&x,&y);
Union(x,y);
}
for(int i=1; i<=N; i++)
if(i==fa[i])
ans++;
printf("%d\n",ans-1);
}
return 0;
}
POJ1988
题目大意:方块编号1~N,开始时每个方块相当于一个栈,执行P个操作,每个操作为M X Y,将包含X的栈的整体移动到包含Y的栈顶部,C X,查询X方块下的数量,输出每个C操作的结果
思路:本题一开始看上去是直接用栈来模拟,但由数据量可知这必然超标,如果将一个个方块视做个体,将M操作视为合并个体,C操作视为查询X的子树和,便可以将本题转换为并查集问题。每次的操作为把一个集合A移动到另一个集合B的上面,那么集合A中的元素其下元素个数便增加Y的容量的个数,对AB做一次合并,设置A集合的父为B,这样的话统计X其下个数便等价于在X所属集合中在X下方的元素个数+在X所属集合下方的元素个数
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int P,fa[121212],d[121212],cnt[121212];
int Find(int x) {
int fx=fa[x];
if(x!=fa[x]) {
fa[x]=Find(fa[x]);
d[x]+=d[fx];
}
return fa[x];
}
void Union(int x,int y) {
int _x=Find(x),_y=Find(y);//获得父节点
fa[_x]=_y;//合并集合
d[_x]=cnt[_y];//将_x集合的数字下方数字个数设置为cnt[_y]
cnt[_y]+=cnt[_x];//因为已经合并,所以可以直接加
}
int main() {
scanf("%d",&P);
for(int i=1; i<=P; i++) {
fa[i]=i;
cnt[i]=1;
}//初始化父节点和每个栈存放了多少个
for(int i=1; i<=P; i++) {
char ch;
int a,b;
getchar();
scanf("%c",&ch);//获得操作
if(ch=='M') {
scanf("%d%d",&a,&b);
Union(a,b);
} else {
scanf("%d",&a);
Find(a);//必须使用一遍Find,因为这样才能在合并的过程中更新d
printf("%d\n",d[a]);
}
}
return 0;
}
POJ1182
题目大意:略
思路:用编号在关系树中的深度来表示编号在循环链中所处的位置,对于每一个节点,它自己的深度为父辈节点深度累和,即 ( d [ x ] = d [ x ] + d [ f a [ x ] ] ) % 3 (d[x]=d[x]+d[fa[x]])\%3 (d[x]=d[x]+d[fa[x]])%3但是循环关系的节点只有三个,所以深度取值为0,1,2,对于两棵树的合并,以查询中涉及到的两个节点为基准,两个节点的深度差由操作给定,以被吃(第二)节点的集合为基准,将吃(第一)节点的集合与它合并,并以关系节点为基准进行上移更改第一集合根节点在第二集合根节点的深度,即 d [ a ] = ( d [ y ] − d [ x ] + 3 + D − 1 ) % 3 d[a]=(d[y]-d[x]+3+D-1)\%3 d[a]=(d[y]−d[x]+3+D−1)%3,D为输入的操作,d[y]-d[x]可得深度差(0代表对齐),D-1可得因为操作所带来的深度差变换(0代表x与y应该对齐,其余的代表x的集合应该上移多少),上移操作如图,以x与y同类为例,其余见代码
代码
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
int fa[121212],d[121212],K,N,D,X,Y,ans;
int Find(int x) {
int fx=fa[x];
if(fa[x]!=x) {
fa[x]=Find(fa[x]);
d[x]=(d[x]+d[fx])%3;//回溯的时候累和
}
return fa[x];
}
bool Union(int x,int y) {
if(x>N||y>N||(D==2&&x==y))//如果超出范围或者自己吃自己
return 0;
int _x=Find(x),_y=Find(y);//获得所属的集合
if(_x==_y&&((d[x]-d[y]+3)%3!=D-1))//如果同一集合并且两者间关系不符合给定
return 0;
fa[_x]=_y;//合并
d[_x]=(d[y]-d[x]+3+D-1)%3;//上移集合根节点的层数
return 1;
}
int main() {
scanf("%d%d",&N,&K);
for(int i=1; i<=N; i++)
fa[i]=i;
while(K--) {
scanf("%d%d%d",&D,&X,&Y);
if(!Union(X,Y))
ans++;
}
printf("%d",ans);
return 0;
}
POJ1703
题目大意:两个帮派,每次给出两种操作,A a b 代表 a,b互异,D a b 代表查询a,是否同一集合
思路:因为每次给出的是互异关系,可以将a,a+n设置为a与a的异,对b同理,每次给出关系时合并a,b+n与b,a+n即可
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
int fa[212121],T,N,M;
int Find(int x) {
if(x==fa[x])
return x;
return fa[x]=Find(fa[x]);
}
void Union(int x,int y) {
int _x=Find(x),_y=Find(y);
if(_x!=_y)
fa[_x]=fa[_y];
}
int main() {
scanf("%d",&T);
while(T--) {
scanf("%d%d",&N,&M);
for(int i=1; i<=2*N; i++)
fa[i]=i;
getchar();
char ch;
int a,b;
while(M--) {
scanf("%c%d%d",&ch,&a,&b);
if(ch=='D') {//合并
Union(a,b+N);
Union(b,a+N);
} else {
if(Find(a)==Find(b)||Find(a+N)==Find(b+N))//判断是否为同一集合
printf("In the same gang.\n");
else if(Find(a)==Find(b+N)||Find(b+N)==Find(a))//判断是否互异
printf("In different gangs.\n");
else
printf("Not sure yet.\n");
}
getchar();
}
}
return 0;
}
合并优化
若高树合并到矮树上,合并后的树高为树高+1,矮树合并到高树,树高不变,因此选择矮->高更划算,树高影响递归效率
void Union(int x,int y) {
int _x=Find(x),_y=Find(y);
if(_x==_y)
return;
if(h[_x]>h[_y])
fa[_y]=_x;
else {
fa[_x]=_y;
if(h[_x]==h[_y])
h[_y]++;
}
}
总结
路径压缩和合并是并查集的基础,而将问题转换为并查集,用树深度代替关系,设置“影子”是在并查集基础上的拓充用法,很巧妙,需要掌握