并查集,顾名思义,就是查找集合然后合并。主要思想就是通过find-union函数实现的,所有的元素都用一个数组表示。如果两个元素属于一个集合,就用find函数将两个元素划分到一个集合中。属于同一集合的元素构成一棵树,如果两个元素的根相同,则属于同一集合。
在ACM常见题中,主要分为两种情况:
1.集合数量未知。
2.集合数量已知。
对于集合数量未知的情况,根据题意修改一下find-union函数,然后将两个元素(或者别的什么,根据题意)划分到一个集合中即可。
对于集合数量已知的情况,需要将元素分到特定的集合中,表示方法也不是像上种情况那样,这也是本文重点要讲解的。
这类题根据题意,了解要分为多少个集合,然后将确定关系的元素划分到一个集合中,如果两者根不同,则二者还没有确定关系。如果二者根相同,则二者有关系,但关系不确定。这里可以用一个公式来区分二者的关系:
rela[x] = (rela[x] + rela[uf[x]) % MOD;
uf[]是记录x的父亲节点的,rela[]是记录x与其父亲的关系的,MOD是划分集合的数量。
每个元素都记录与其父亲的关系,在整个树上就能判断任意两者的关系了。
当两者确定关系后,将两个元素连到一个树里,要用到另一个公式:
rela[px] = (rela[y] - rela[x] + MOD) % MOD;
px是x的根节点。
关于第二个公式的推导:
fx->fy = fx->x + x->y + y->fy;
这里用到了向量的思想,分解了路径,从而求出未知的路径。
下面是find-union函数的模板。
//find函数</span>
uf_find(int x)
{
if (uf[x] == x)
return x;
int tmp = uf[x];
uf[x] = uf_find(tmp);
sign[x] = (sign[tmp] + sign[x]) % MOD;
return uf[x];
}
//union函数
void uf_union(int a, int b, int pa, int pb)
{
uf[pa] = pb;
sign[pa] = (sign[b] - sign[a]) % MOD;
}
如何判断两个元素的关系judge函数:
根据rela[]可以判断x与其父亲的关系,从x一直加到根,然后modMOD,就可以得出x与根的关系,y也如此,然后就可以通过判断x和y的值判断二者的关系了。
下面是judge函数的实现:
//judge函数
void uf_judge(int n)
{
int x, tmp;
for (int i = 1; i <= n; i++)
{
x = i;
while (x != uf[x])
{
tmp = uf[x];
judge[i] = (sign[x] + judge[i]) % MOD;
x = tmp;
}
}
}
注:
由于判断两者关系的机制博主没看太懂,从而写了这个judge函数,对于两个集合的情况已经验证过了,其他情况还没有证实过,但估计应该没错。
还有,find-union函数还可以继续优化,比如union的按秩合并,但是这种启发式的优化不明显,所以这里不介绍了。