看题链接:http://poj.org/problem?id=2492
这也算是种类并查集的一道经典例题了吧,题意就不多解释了,先写一些我对种类并查集的一些理解。
种类并查集比普通的并查集多一个relation数组,relation[i] 记录了 i 和 其直接父亲节点的关系,这个关系的表示因题目而异,种类并查集的重点和难点就是对这个relation数组的维护。种类
在这个题中,relation数组具体表示是:以0表示和父亲节点的性别相同,1表示和父亲节点的性别不同。初始时可设初值全部为0。在Find函数中进行路径压缩时,要注意同时维护relation数组,如果relation[i] 和 relation[ father[i] ] 的值相同( 此时relation[ father[i] ] 实际上表示的是 i 的 父亲节点与 i 的祖先节点之间的关系)都是1 或者都是 0,可以推测出此时若将 i 链接到其祖先节点的话,relation[i] 应该变为0。若值不相同,则应变为1;
在Union函数进行合并中,首先看看x 和 y是否在同一个集合中。如果在同一个集合中,那么再判断他们相较于祖先节点的关系,如果关系相同说明是同性恋。如果不在同一个集合里,就可以合并。合并之后要继续考虑这时relation数组的变化。在我们寻找变化规律之前,让我们再仔细的看看relation数组,如果你和我一样看过很多人的题解(汗……)你会发现很多人使用Rank数组来表示relation,其实这也是relation数组的本质,relation数组其实表示的正是节点的偏移量,不过不是递增或递减变化,而是0和1交替变化。如果明白了这一点,Union函数中的relation数组变化是不是就迎刃而解了呢?对!我们只要将两个节点的偏移量相减后再加一再%2,得到的就是新的偏移量。
也正是如此,种类并查集其实是带偏移量的并查集。
下面上一波C艹实现
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 2500;
int fa[MAXN], relation[MAXN];
bool flag;
void Init(int n){
for(int i = 0;i <= n;++i){
fa[i] = i;
relation[i] = 0;
}
}
int Find(int x){
if(fa[x] == x)
return x;
int temp = Find(fa[x]);
relation[x] = (relation[x] + relation[fa[x]] ) % 2;
fa[x] = temp;
return temp;
}
void Union(int x, int y){
int faX = Find(x), faY = Find(y);
if(faX == faY){
if(relation[x] == relation[y]){
flag = true;
return;
}
}
fa[faX] = faY;
relation[faX] = (relation[x] - relation[y] + 1 ) % 2; // 这里的+1其实可以认为是当faX连入faY时,x相对于faY的偏移量又加一。
}
int main()
{
//freopen("input.txt", "r", stdin);
int T;
scanf("%d", &T);
for(int i = 1;i <= T;++i){
int n, k;
flag = false;
scanf("%d %d",&n, &k);
Init(n);
for(int i = 0;i < k;++i){
int a, b;
scanf("%d %d",&a, &b);
if(flag)
continue;
Union(a, b);
}
printf("Scenario #%d:\n",i);
if(flag)
printf("Suspicious bugs found!\n");
else
printf("No suspicious bugs found!\n");
printf("\n");
}
return 0;
}