这是一道经典的带权并查集例题。
我们定义权值s[i]表示节点i与根的关系:
0:和根是同一种动物
1:i能够吃根节点
2:i被根节点吃
这样定义的好处就是,在模3的意义下,各个节点之间的关系能够传递
(接下来本题解所讨论的范围都是在模3的意义下)
例如:
这样我们的权值s[i]就能满足一维向量加法了。
那么很显然,s[i]也满足一维向量减法:
a与b的关系就是s[a]-s[b]
再来考虑如何解此题:
若给出的X,Y未确定关系(X,Y拥有不同的根)显然这句话是对的,
然后把X,Y所在集合合并。
这里讲一下合并时权值如何修改:
设当前语句的条件是t(即x与y的关系),x、y的根分别是fx,fy
有:s[x]+?=t+s[y]
即:?=t+s[y]-s[x]
那么合并就是这样的:
fa[fx]=fy s[fx]=t+s[y]-s[x]
剩下的情况就是不用修改的,注意这时X,Y已经有公共祖先了
可以得到X,Y的关系是s[x]-s[y]
只需判断s[x]-s[y]是否与给定关系t相等即可
代码:
#include<cstdio>
#include<cstring>
const int maxn=50005;
int n,q,fa[maxn],s[maxn],ans=0;
inline int red(){
int tot=0;char ch=getchar();
while (ch<'0'||'9'<ch) ch=getchar();
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
return tot;
}
int getfa(int x){
if (fa[x]==x) return x;
int father=fa[x];
fa[x]=getfa(fa[x]);
s[x]=(s[x]+s[father])%3;
return fa[x];
}
int main(){
n=red(),q=red();
for (int i=1;i<=n;i++) fa[i]=i;
while (q--){
int ch=red(),x=red(),y=red();
if (x>n||y>n) {ans++;continue;}
if (ch==1){
int fx=getfa(x),fy=getfa(y);
if (fx!=fy) fa[fx]=fy,s[fx]=(s[y]-s[x]+3)%3;else ans+=(s[x]-s[y]+3)%3!=0;
}else{
int fx=getfa(x),fy=getfa(y);
if (fx!=fy) fa[fx]=fy,s[fx]=(s[y]-s[x]+4)%3;else ans+=(s[x]-s[y]+3)%3!=1;
}
}
printf("%d",ans);
return 0;
}
本文详细介绍了如何使用带权并查集解决NOI2001的题目食物链,讨论了权值的定义及其在模3意义下的关系传递性。通过权值的加减运算,阐述了解决此类问题的关键步骤,包括集合合并时权值的修改,并提供了相应的代码实现。
2292

被折叠的 条评论
为什么被折叠?



