带(加)权并查集思想:
相比于简单的并查集,带权并查集中各个节点之间的关系更为复杂。
带(加)权并查集实现:
带(加)权并查集的实现同简单并查集并没有什么不用,但是带(加)权并查集的关系合并操作中会比多用到模除或者是异或操作,具体因题而异。
例题:
题目地址:食物链 POJ - 1182
解析:
本题作为带(加)权并查集经典题目,代码中的合并操作在主函数中完成了。而且通过向量思维的解释,该题的逻辑也更容易理解。
向量思维:
1.若rootx与rooty不同,则:
rootx->rooty = rootx->x + x->y + y->rooty
即rootx->rooty = (relation[x]+d-1+3-relation[y])%3 = relation[rooty]
2.如果rootx和rooty相同,则:
x->y = x->rootx + rootx->y
即x->y = (3-relation[x]+relation[y])%3
下面直接上代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N=50000;
struct Node{
int fa; //父节点
int re; //与父节点的关系,0为同类,1为被父节点吃,2为吃父节点
} ani[N+5];
int n,k;
void UFInit()
{
for(int i=1;i<=n;i++)
{
ani[i].fa=i;
ani[i].re=0;
}
}
int Find(int a)
{
if(ani[a].fa==a)
return a;
int tem=ani[a].fa;
ani[a].fa=Find(tem); //路径压缩
ani[a].re=(ani[a].re+ani[tem].re)%3; //穷举法推出
//子与爷的关系通过子与父和父与爷推出,故需中间变量保存父
return ani[a].fa;
}
int main()
{
int d,x,y,ans=0;
scanf("%d%d",&n,&k);
UFInit();
while(k--)
{
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n) //条件二
{
ans++;
continue;
}
if(d==2&&x==y) //条件三
{
ans++;
continue;
}
int rootx=Find(x);
int rooty=Find(y);
//向量法中,ani[x].re表示rootx~x
//根据ani[x].re可反向推出x~rootx的关系,即(3-ani[x].re)
if(rootx!=rooty) //合并
{
ani[rooty].fa=rootx;
ani[rooty].re=(ani[x].re+(d-1)+(3-ani[y].re))%3;
//向量法,rootx~rooty=(rootx~x + x~y + y~rooty)%3
}
else
{
if(d==1&&ani[x].re!=ani[y].re)
ans++;
else if(d==2&&((3-ani[x].re)+ani[y].re)%3!=d-1)
//向量法,x~y=(x~rootx + rooty~y)%3
ans++;
}
}
printf("%d\n",ans);
return 0;
}