带权并查集
带权并查集是结点存有权值信息的并查集。权值使关系可以量化,也就是说,权值代表着当前节点与父节点的某种关系,通过两者关系,也可以将同一棵树下两个节点的关系表示出来。而一般并查集只能判断属于某个集合。
种类并查集
一般并查集可以判断一种关系,即属于这种关系或不属于这种关系,比如朋友的朋友是朋友。但比如敌人的敌人是朋友这种涉及两种以上的关系就会用到种类并查集,其实就是用多个并查集来模拟种类。
例题
传送门: POJ-1401
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
input:
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
output:
只有一个整数,表示假话的数目。
Sample Input:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output:
3
分析
-
带权并查集解法
定义权值数组rela[]描述与根节点的关系,0表示同类,1表示当前点能吃别人,2表示当前点被别人吃。通过向量思想维护root[]和rela[]关系来判断与之前关系是否矛盾。
ps: 详细向量图解 -
种类并查集解法
开一个3n大小的数组,用x、x+n、x+2n来表示3个不同的种类。比如x+n代表x的天敌,x+2n是x+n的天敌,x是x+2n的天敌然后模拟维护数组并判断即可。(当然代表猎物也可以,源码用的天敌)
代码
- 带权并查集解法
#include<cstdio>
using namespace std;
const int maxn = 50004;
int root[maxn], rela[maxn];
int r, x, y, n, k;
int Find(int x) {
if (x == root[x])return x;
int tmp = root[x];
root[x] = Find(root[x]);//压缩路径
//x与根节点关系=x到父节点的关系+父节点(tmp)到根节点的关系
rela[x] = (rela[x]+rela[tmp]+ 3) % 3;
return root[x];
}
bool check(int r, int x, int y) {
if (x > n || y > n || (r == 1 && x == y))
return false;
if (Find(x) == Find(y))
//判断x与y关系(r)等于x到根关系+根到y关系(~rela[y])
return r == (rela[x] - rela[y] + 3) % 3;
else return true;
}
void Union(int r, int x, int y) {
int fx = Find(x), fy = Find(y);
if (fx != fy) {
root[fx] = fy;//x根并入y根集合
//更新x根到新根节点关系
//x根到新根节点关系=根到x的关系(~relx[x])+x与y关系(r)+y到根关系
rela[fx] = (-rela[x] + r + rela[y] + 3) % 3;
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i <= n; i++) {//初始化
root[i] = i;
rela[i] = 0;
}
int ans = 0;
while (k--) {
scanf("%d%d%d", &r, &x, &y);
r--;
if (check(r, x, y))
Union(r, x, y);
else ans++;
}
printf("%d\n", ans);
return 0;
}
- 种类并查集解法
#include<cstdio>
using namespace std;
const int maxn = 50004;
int root[maxn * 3];
int r, x, y, n, k;
int Find(int x) {
return x == root[x] ? x : root[x] = Find(root[x]);
}
bool check(int r, int x, int y) {
if (x > n || y > n || (r == 1 && x == y))
return false;
int fx = Find(x), fy = Find(y);
int fyn = Find(y + n);
int fynn = Find(y + n + n);
if (r == 0) {//同类
if (fx == fyn || fx == fynn)
return false;
}
else {//x吃y
if (fx == fy || fx == fynn)
return false;
}
return true;
}
void Union(int r, int x, int y) {
int fx = Find(x), fy = Find(y);
int fxn = Find(x + n), fyn = Find(y + n);
int fxnn = Find(x + n + n), fynn = Find(y + n + n);
if (fx != fy) {
if (r == 0) {//x和y是同类
root[fx] = fy; //x的同类和y的同类是同类
root[fxn] = fyn; //x的天敌和y的天敌是同类
root[fxnn] = fynn;//x的猎物和y的猎物是同类
}
else {//x吃y
root[fx] = fyn; //x的同类和y的天敌是同类
root[fxn] = fynn;//x的天敌和y的猎物是同类
root[fxnn] = fy; //x的猎物和y的同类是同类
}
}
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i <= 3 * n; i++)root[i] = i;//初始化
int ans = 0;
while (k--) {
scanf("%d%d%d", &r, &x, &y);
r--;
if (check(r, x, y))
Union(r, x, y);
else ans++;
}
printf("%d\n", ans);
return 0;
}
小结
- 如果关系(种类)过多,那么种类并查集写起来很繁琐,带权并查集更占优势。
- 种类并查集还需要足够的空间,对空间限制小的题也是硬伤。
- 但是种类并查集更容易理解和模拟,带权并查集需要充分理解其向量思维。
原创不易,请勿转载(
本不富裕的访问量雪上加霜)
博主首页:https://blog.csdn.net/qq_45034708
如果文章对你有帮助,记得关注点赞收藏❤