开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
之前已经简单介绍过了并查集,如果并查集维护了集合内元素之间关系的有无,在我们给边附加了权值之后,加权并查集就可以维护“关系是什么”了。下面我们还是从一个经典例题入手对加权并查集进行简单说明。
问题定义
让我们直接来看一道题目。
题目链接
P2024 [NOI2001] 食物链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目大意
思路
没错就是超级经典的食物链qwq~
虽然可以用扩展域并查集来实现,但是我们在这里用它为载体来讲解加权并查集。我们刚才说了:
加权并查集可以维护“关系是什么”
在本题中需要维护的关系三种,我们开一个数组 进行记录:
- ,表示 吃 。
- ,表示 被 吃。
- ,表示 和 是同类。
查询
那么我们应该怎样通过 和 之间的关系得到 和根节点之间的关系呢?
对于上图,我们可以列出如下关系表:
解释 | |||
---|---|---|---|
0 | 0 | 0 | A 和 B 是同类,B 和 C 是同类,所以 A 和 C 是同类。 |
0 | 1 | 1 | A 和 B 是同类,B 吃 C,所以 A 吃 C。 |
0 | 2 | 2 | A 和 B 是同类,B 被 C 吃,所以 A 被 C 吃。 |
1 | 0 | 1 | A 吃 B,B 和 C 是同类,所以 A 吃 C。 |
1 | 1 | 2 | A 吃 B,B 吃 C,所以 A 被 C 吃。 |
1 | 2 | 0 | A 吃 B,B 被 C 吃,所以 A 和 C 是同类。 |
2 | 0 | 2 | A 被 B 吃,B 和 C 是同类,所以 A 被 C 吃。 |
2 | 1 | 0 | A 被 B 吃,B 吃 C,所以 A 和 C 是同类。 |
2 | 2 | 1 | A 被 B 吃,B 被 C 吃,所以 A 吃 C。 |
容易发现 ,我们找根的递归过程中应每次进行转移,所以我们寻找代表元素时路径压缩的代码可以实现为:
int find(int x)
{
if (x==f[x]) return x;
int t=find(f[x]);
w[x]=(w[x]+w[f[x]])%3;
return f[x]=t;
}
判断与合并
由于我们转移 时进行了路径压缩,所以我们执行完 之后, 就是 所在集合的代表元素, 就是 与其所在集合代表元素之间的关系值。
如果 和 在同一个集合中,我们需要判断他们两个之间的关系是否和输入的一致。 如果 和 不在同一个集合中,我们需要将两个集合合并。具体判断方式也可以利用列表格或简单推理求出,详见代码。
代码
#include <iostream>
using namespace std;
int f[50001],w[50001],n,k,x,y,z,ans,i;
int find(int x)
{
if (x==f[x]) return x;
int t=find(f[x]);
w[x]=(w[x]+w[f[x]])%3; //执行完find后,x的父亲指向代表元素,w[t]得到更新,更新w[x]
return f[x]=t;
}
int main()
{
cin>>n>>k;
for (i=1;i<=n;++i) f[i]=i;
for (i=1;i<=k;++i)
{
cin>>z>>x>>y;
if (x>n||y>n) ans++;
else
{
if (find(x)!=find(y))//若x和y不在一个并查集中,将其所在并查集合并
{
if (w[x]==0) w[f[x]]=z-1;//若x和它的祖先是同类,则x和y的关系即为x的祖先与y的关系
else if (w[x]==z-1) w[f[x]]=0;//若x和它的祖先的关系和x和y的关系相同,则x的祖先和y是同类
else if (z==1) //若x和y是同类
{//若x吃x的祖先,则x的祖先被y吃
//若x被x的祖先吃,则x的祖先吃y
if (w[x]==1) w[f[x]]=2;
if (w[x]==2) w[f[x]]=1;
}
else w[f[x]]=2;//此时w[x]!=0,z!=1,w[x]!=z-1
//所以z=2,即z-1=1,w[x]=2,
//即x吃y,x被x的祖先吃,所以x的祖先被y吃
f[f[x]]=y;//令x的祖先的父亲为y
}
else //若x和y在一个并查集中,查询新关系是否与其旧关系冲突
{
if (z==2&&w[x]-w[y]!=1&&w[x]-w[y]!=-2) ans++;//若x与其祖先的关系和y与其祖先的关系相同,则说明x和y是同类
if (z==1&&w[x]!=w[y]) ans++;//当。。。被满足时,x吃y
}
}
}
cout<<ans;
return 0;
}
Other
Codeforces Round #836 (Div. 2) E. Tick, Tock 可以用加权并查集做,后续更新题解。