并查集的作用:并和查,即合并和查找,将一些集合合并,快速查找或判断某两个集合的关系,或某元素与集合的关系,或某两个元素的关系。
并查集的结构:并查集主要操作对象是森林,树的结构赋予它独特的能力,对整个集合操作转换为对根节点(或称该集合的代表元素)的操作,一个集合里的元素关系不一定确定,但相对于根节点的关系很明了,这也是为了查找方便。
并查集优化方法:按秩合并和路径压缩的配合使用,使得查找过程优化到极致。按秩合并,每次将深度小的树合并到深度大的树里面去,使得整棵树尽量矮;路径压缩,将当前节点到根节点路径上的所有点直接连到根节点上(有递归和非递归两种方法),使得每个点到根节点的距离更短,在下一次查找的时候更快。
如何快速确定偏移量公式:
例:现在要合并节点x,y, 找到根节点fx = Find(x); fy = Find(y);一般情况下,根节点的偏移量都保持为0, offset[foot] = 0;如果要使得x和y的偏移量为t,假设fx指向fy,则可以写出公式offset[x] + offset[fx] - offset[y] = t,则offset[fx] = (offset[y] + t - offset[x]) % n; 这个n即为总共有多少类,如:在poj1182 食物链中n = 3,,在poj2492 A Bug's Life中n = 2, 这样fx的偏移量就计算出来了,只需要改其中一个根节点的偏移量,这里是fx,因为假设是fx指向fy。
对应题目和代码


#define N 3005
int bin[N],cnt[N];
int find(int x)
{
int r= x;
while(bin[r]!= r)
r= bin[r];
int y= x;
while(bin[y]!= y){
y= bin[y];
bin[y]= r;
}
return r;
}
void merge(int x,int y)
{
int fx= find(x);
int fy= find(y);
if(fx!= fy){
if(fx==0 ){
bin[fy]= fx;
cnt[fx]+= cnt[fy];
}
else if(fy==0 ){
bin[fx]= fy;
cnt[fy]+= cnt[fx];
}
else {
bin[fx]= fy;
cnt[fy]+= cnt[fx];
}
}
}
int main()
{
int n,m,num,cur,next,i;
while(scanf("%d%d",&n,&m),n+ m){
for(i=0;i<n;i++ )
bin[i]=i, cnt[i]=1 ;
while(m-- ){
scanf("%d%d",&num,& cur);
while(-- num){
scanf("%d",& next);
merge(cur,next);
cur= next;
}
}
printf("%d\n",cnt[0 ]);
}
return 0 ;
}
分析:很经典的一道并查集,这题关键在如何维护每一个点到集合顶点的偏移量
。第一次了解偏移量,还是挺有收获的!
对于两个点x,y,分别找到他们的根节点fx,fy。(fx = find(x); fy = find(y);)
如果fx == fy 则找到一对同性恋者,return,
否者:bin[fx] = fy; fx指向fy,为了保证x和y为异性,即相对根节点的偏移量不同,
fx的偏移量offset[fx] = (offset[x] + offset[y] + 1)%2; 是同或关系,
这样只对根节点操作,就不影响其它各点间的相互关系。
还有:在路径压缩的过程中,采用递归形式,由根节点反向改变每个叶子节点的值,
这样只用比较当前节点和他父节点之间的关系了:offset[r] = (offset[r] + offset[bin[r]]) % 2;是异或关系。


#include < string .h >
int bin[ 2002 ],offset[ 2002 ],ok;
int find( int x)
{
int r = x;
if (r == bin[r])
return r;
int t = find(bin[r]);
offset[r] = (offset[r] + offset[bin[r]]) % 2 ;
bin[r] = t;
return t;
}
void merge( int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx == fy && offset[x] == offset[y])
ok = 1 ;
else
{
bin[fx] = fy;
offset[fx] = (offset[x] + offset[y] + 1 ) % 2 ;
}
}
int main()
{
int i,cas,ca,n,m,x,y;
scanf( " %d " , & cas);
for (ca = 1 ;ca <= cas;ca ++ )
{
scanf( " %d%d " , & n, & m);
for (i = 0 ;i < n;i ++ )
bin[i] = i, offset[i] = 0 ;
ok = 0 ;
while (m -- )
{
scanf( " %d%d " , & x, & y);
if (ok == 1 )
continue ;
merge(x,y);
}
printf( " Scenario #%d:\n " ,ca);
if (ok == 1 )
puts( " Suspicious bugs found! " );
else
puts( " No suspicious bugs found! " );
puts( "" );
}
return 0 ;
}


int a[ 1002 ];
int find( int x)
{
int r = x;
while (a[r] != r)
r = a[r];
return r;
}
void merge( int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx != fy)
a[fx] = fy;
}
int main()
{
int n,m,i,x,y,ans;
while (scanf( " %d " , & n),n)
{
for (i = 1 ;i <= n;i ++ )
a[i] = i;
scanf( " %d " , & m);
while (m -- )
{
scanf( " %d%d " , & x, & y);
merge(x,y);
}
for (ans =- 1 ,i = 1 ;i <= n;i ++ )
{
if (a[i] == i)
ans ++ ;
}
printf( " %d\n " ,ans);
}
return 0 ;
}
广搜的方法 15MS


#include < iostream >
using namespace std;
char map[ 200 ][ 200 ];
char str[ 52 ][ 52 ];
typedef struct {
int x, y;
}POINT;
POINT p, next;
int n,m,dir[ 8 ][ 2 ] = {{ - 1 , - 1 }, { - 1 , 0 }, { - 1 , 1 }, { 0 , - 1 }, { 0 , 1 }, { 1 , - 1 }, { 1 , 0 }, { 1 , 1 }};
queue < POINT > q;
void bfs( int i, int j)
{
int k;
p.x = i; p.y = j;
q.push(p);
while ( ! q.empty())
{
p = q.front();
q.pop();
for (k = 0 ; k < 8 ; k ++ ) {
next.x = p.x + dir[k][ 0 ];
next.y = p.y + dir[k][ 1 ];
if (next.x >= 0 && next.x <= 3 * m && next.y >= 0 && next.y <= 3 * n)
if (map[next.x][next.y] == ' W ' ) {
map[next.x][next.y] = ' . ' ;
q.push(next);
}
}
}
}
int main()
{
int i,j,con;
while (scanf( " %d%d " , & m, & n),m >= 0 && n >= 0 )
{
getchar();
for (i = 0 ;i <= 3 * m;i ++ )
{
for (j = 0 ;j <= 3 * n;j ++ )
map[i][j] = ' . ' ;
}
for (i = 0 ;i < m;i ++ )
{
gets(str[i]);
for (j = 0 ;j < n;j ++ )
{
if (str[i][j] == ' A ' ){map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j] = ' W ' ;}
else if (str[i][j] == ' B ' ){map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;}
else if (str[i][j] == ' C ' ){map[ 3 * i + 1 ][ 3 * j] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;}
else if (str[i][j] == ' D ' ){map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;}
else if (str[i][j] == ' E ' ){map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;}
else if (str[i][j] == ' F ' ){map[ 3 * i + 1 ][ 3 * j] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;}
else if (str[i][j] == ' G ' ){map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;}
else if (str[i][j] == ' H ' ){map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;}
else if (str[i][j] == ' I ' ){map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;}
else if (str[i][j] == ' J ' ){map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;}
else if (str[i][j] == ' K ' ){map[ 3 * i + 1 ][ 3 * j + 2 ] = ' W ' ;map[ 3 * i][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 2 ][ 3 * j + 1 ] = ' W ' ;map[ 3 * i + 1 ][ 3 * j] = ' W ' ;}
}
}
con = 0 ;
for (i = 0 ;i <= 3 * m;i ++ )
{
for (j = 0 ;j <= 3 * n;j ++ )
if (map[i][j] == ' W ' )
{
bfs(i, j);
con ++ ;
}
}
printf( " %d\n " , con);
}
return 0 ;
}
并查集的方法 0MS
这里用的递归法压缩路径,感觉也挺好的,代码很短,以前都是用非递归形式。在合并的时候,按秩合并很重要,每次合并,总把深度小的树合并到深度大的树里面,以防止树退化成链。这样可以减少树的深度,使得查找时间很均匀,更高效的查找。


#include < string .h >
int bin[ 2502 ],rank[ 2502 ];
char map[ 55 ][ 55 ];
char mat1[ 100 ][ 100 ]; // 上下连通
char mat2[ 100 ][ 100 ]; // 左右连通
int up[ 8 ],down[ 8 ],left[ 8 ],right[ 8 ];
void prepare()
{
int i,j;
up[ 0 ] = ' A ' ; up[ 1 ] = ' B ' ; up[ 2 ] = ' E ' ; up[ 3 ] = ' G ' ; up[ 4 ] = ' H ' ; up[ 5 ] = ' J ' ; up[ 6 ] = ' K ' ;
down[ 0 ] = ' C ' ; down[ 1 ] = ' D ' ; down[ 2 ] = ' E ' ; down[ 3 ] = ' H ' ; down[ 4 ] = ' I ' ; down[ 5 ] = ' J ' ; down[ 6 ] = ' K ' ;
left[ 0 ] = ' A ' ; left[ 1 ] = ' C ' ; left[ 2 ] = ' F ' ; left[ 3 ] = ' G ' ; left[ 4 ] = ' H ' ; left[ 5 ] = ' I ' ; left[ 6 ] = ' K ' ;
right[ 0 ] = ' B ' ; right[ 1 ] = ' D ' ; right[ 2 ] = ' F ' ; right[ 3 ] = ' G ' ; right[ 4 ] = ' I ' ; right[ 5 ] = ' J ' ; right[ 6 ] = ' K ' ;
memset(mat1, 0 , sizeof (mat1));
memset(mat2, 0 , sizeof (mat2));
for (i = 0 ;i < 7 ;i ++ )
{
for (j = 0 ;j < 7 ;j ++ )
{
mat1[down[i]][up[j]] = 1 ;
mat2[right[i]][left[j]] = 1 ;
}
}
}
int find( int x)
{
if (x != bin[x])
return bin[x] = find(bin[x]);
return x;
}
void merge( int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx != fy)
{
if (rank[fx] > rank[fy])
bin[fy] = fx;
else if (rank[fx] < rank[fy])
bin[fx] = fy;
else
{
bin[fx] = fy;
rank[fy] ++ ;
}
}
}
int main()
{
int n,m,i,j;
prepare();
while (scanf( " %d%d " , & n, & m),n + m !=- 2 )
{
getchar();
for (i = 0 ;i < n;i ++ )
gets(map[i]);
for (i = 0 ;i < m * n;i ++ )
bin[i] = i, rank[i] = 1 ;
for (i = 0 ;i < n;i ++ )
{
for (j = 0 ;j < m;j ++ )
{
if (j < m - 1 && mat2[map[i][j]][map[i][j + 1 ]] == 1 )
merge(i * m + j,i * m + j + 1 );
if (i < n - 1 && mat1[map[i][j]][map[i + 1 ][j]] == 1 )
merge(i * m + j,i * m + j + m);
}
}
int con = 0 ;
for (i = 0 ;i < m * n;i ++ )
if (bin[i] == i)
con ++ ;
printf( " %d\n " ,con);
}
return 0 ;
}
第一点:如何判断两条线段是否有交点。
第二点:快速查找某条线段所在集合里的线段条数,用并查集实现。


#define N 1005
typedef struct node{
double x,y;
}NODE;
NODE stap[N],endp[N];
int cnt[N],rank[N],bin[N];
int index,n;
void init()
{
index = 1 ;
for ( int i = 1 ;i <= n;i ++ )
{
bin[i] = i;
rank[i] = 1 ;
cnt[i] = 1 ;
}
}
int Find( int x)
{
if (x != bin[x])
return bin[x] = Find(bin[x]);
return x;
}
void Merge( int x, int y)
{
int fx = Find(x);
int fy = Find(y);
if (fx != fy)
{
if (rank[fx] > rank[fy])
{
bin[fy] = fx;
cnt[fx] += cnt[fy];
}
else
{
if (rank[fx] == rank[fy])
rank[fy] ++ ;
bin[fx] = fy;
cnt[fy] += cnt[fx];
}
}
}
int Value(NODE s,NODE e,NODE t)
{
double ans = (e.x - s.x) * (t.y - s.y) - (t.x - s.x) * (e.y - s.y);
if (ans < 0 )
return - 1 ;
else if (ans > 0 )
return 1 ;
else
return 0 ;
}
int Cross(NODE s1,NODE e1,NODE s2,NODE e2)
{
// if(!(Value(s1,e1,s2)*Value(s1,e1,e2)>0) && !(Value(s2,e2,s1)*Value(s2,e2,e1)>0))
if ((Value(s1,e1,s2) * Value(s1,e1,e2) <= 0 ) && (Value(s2,e2,s1) * Value(s2,e2,e1) <= 0 ))
return 1 ;
return 0 ;
}
void Add(NODE s,NODE e)
{
for ( int i = 1 ;i < index;i ++ )
{
if (Cross(s,e,stap[i],endp[i]))
Merge(i,index);
}
}
int main()
{
int cas,x,fx;
char ch[ 4 ];
scanf( " %d " , & cas);
while (cas -- )
{
scanf( " %d " , & n);
init();
while (n -- )
{
scanf( " %s " ,ch);
if (ch[ 0 ] == ' P ' )
{
scanf( " %lf%lf%lf%lf " , & stap[index].x, & stap[index].y, & endp[index].x, & endp[index].y);
Add(stap[index],endp[index]);
index ++ ;
}
else {
scanf( " %d " , & x);
fx = Find(x);
printf( " %d\n " ,cnt[fx]);
}
}
if (cas)
puts( "" );
}
return 0 ;
}
一组测试实例,写成多组的话会WA,道理同PKU2492一样,用到偏移量


#define N 50005
int bin[N],offset[N];
int ans;
int find( int x)
{
if (x != bin[x])
{
int t = find(bin[x]);
offset[x] = (offset[x] + offset[bin[x]]) % 3 ;
return bin[x] = t;
}
return x;
}
void merge( int d, int x, int y)
{
int fx = find(x);
int fy = find(y);
if (fx == fy)
{
if (d == 1 )
{
if (offset[x] != offset[y])
ans ++ ;
}
else
{
if ((offset[x] - offset[y] + 3 ) % 3 != 1 )
ans ++ ;
}
}
else
{
if (d == 1 ){
bin[fx] = fy;
offset[fx] = (offset[y] - offset[x] + 3 ) % 3 ;
}
else {
bin[fx] = fy;
offset[fx] = (offset[y] - offset[x] + 4 ) % 3 ;
}
}
}
int main()
{
int n,k,i,d,x,y;
scanf( " %d%d " , & n, & k);
ans = 0 ;
for (i = 1 ;i <= n;i ++ )
{
bin[i] = i;
offset[i] = 0 ;
}
while (k -- )
{
scanf( " %d%d%d " , & d, & x, & y);
if (x > n || y > n || (d == 2 && x == y)){
ans ++ ;
continue ;
}
merge(d,x,y);
}
printf( " %d\n " ,ans);
return 0 ;
}
拓扑排序+并查集