重要思路如下图所示,细节见代码注释,不懂的见往期博客关于并查集的文章
文字版
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 设A是同类,B是食物,C是天敌 同类吃食物 食物吃天敌 天敌吃同类
思路:开3N的并查集,1~N是同类,N+1~2N是食物,2N+1~3N是天敌
即 x本身为i(i∈[1,n]) x的食物为i+n x的天敌为i+2*n 这是种类并查集的性质1
重难点1:合并算法 {推导见重难点2
X和Y是同类:X本身与Y本身是同类 X的食物与Y的食物是同类(易忽略致误) X的天敌与Y的天敌是同类(易忽略致误)
即 merge(x,y) merge(x+n,y+n) merge(x+2*n,y+2*n)
X吃Y: X本身是Y本身的天敌 X的食物是Y的同类 x的天敌是y的食物(易忽略致误)
即 merge(x,y+2*n) merge(x+n,y) merge(x+2*n,y+n)
}
重难点2:同属一个根节点的数学意义:{ 种类并查集的性质2,以此推出merge算法
i.同属一个根节点等价于属于同一个并查集
ii.在同一个种类区间内(比如n=5,则1-5是一个种类区间,6-10是另一个种类区间,[1+kn,(k+1)n]是一个种类区间),若两个元素同属一个并查集,则他们是同类
iii.在不同的种类区间内,若两元素同属一个并查集,则表示一种捕食的关系。
find(x) ==find(y+2*n),find(x+n)==find(y),find(x+2*n)==find(y+n) 满足任意一种则表示X吃Y
find(y) ==find(x+2*n),find(y+n)==find(x),find(y+2*n)==find(x+n) 满足任意一种则表示Y吃X
经1吃2,2吃3验证,3吃1的三种情况均出现了 因此,判断悖论时选最简单的一种即可,比如中间一列情况
}
判断第一种错误:
如果x与y是同类 那么若find(x)==find(y+n)(x吃y)或find(x+n)==find(y)(y吃x) 则错误
如果x吃y 那么若find(x)==find(y)(x,y同类)或find(x)==find(y+n)(y吃x) 则错误
#include<cstdio>
#include<iostream>
using namespace std;
const int N=5e4+1,K=1e5+1;
int fa[3*N],rank[3*N]; //种类并查集 三种
int ans=0;//假话数
inline int read() {
int s=0,w=1;s=0一定要写,除非是全局变量,否则不会初始化!!!
char c=getchar();
while(c<'0' || c>'9') {
if(c=='-') w*=-1;
c=getchar() ;
}
while(c>='0' && c<='9') s=(s<<3)+(s<<1)+c-'0',c=getchar();
return s*w;
}
int find(int x) { //找根节点 路径压缩
return (x==fa[x]) ? x : (fa[x]=find(fa[x]));//按秩合并和路径压缩在可以破坏树的结构情况下可以共存
}
void init(int n) { //初始化
for(int i=0;i<=3*n;i++){
fa[i]=i;rank[i]=0;
}
}
void merge(int x,int y) { //按秩合并
int fx=find(x),fy=find(y);
if(rank[fx]>=rank[fy]){
fa[fy]=fx;
}else fa[fx]=fy;
if(rank[fx]==rank[fy] && fx!=fy) rank[fx]++;
}
int main() {
int n=read(),k=read();
init(n);
for(int i=1; i<=k; i++) {
int d=read(),x=read(),y=read();
//先判断是否为悖论,再合并
if(x>n || y>n) { //2) 当前的话中X或Y比N大,就是假话;
ans++;
// cout<<"悖论2ans="<<ans<<endl;
continue;
}
if(d==2 && x==y) { //3) 当前的话表示X吃X,就是假话。
ans++;
// cout<<"悖论3ans="<<ans<<endl;
continue;
}
/* 判断第一种错误:
如果x与y是同类 那么若find(x)==find(y+n)(x吃y)或find(x+n)==find(y)(y吃x) 则错误
如果x吃y 那么若find(x)==find(y)(x,y同类)或find(x)==find(y+n)(y吃x) 则错误
合并算法 X和Y是同类:X本身与Y本身是同类 X的食物与Y的食物是同类(易忽略致误) X的天敌与Y的天敌是同类(易忽略致误)
即 merge(x,y) merge(x+n,y+n) merge(x+2*n,y+2*n)
X吃Y: X本身是Y本身的天敌 X的食物是Y的同类 x的天敌是y的食物(易忽略致误)
即 merge(x,y+2*n) merge(x+n,y) merge(x+2*n,y+n) */
if(d==1){
if(find(x)==find(y+n) || find(x+n)==find(y)){//易错点!!括号里是find,要用根节点,不能fa[]用父节点,虽然示例能过吧
ans++;
// cout<<"!ans="<<ans<<endl;
continue;
}
merge(x,y),merge(x+n,y+n),merge(x+2*n,y+2*n);
}else {
if(find(x)==find(y) || find(x)==find(y+n)) {
ans++;
// cout<<"!!!ans="<<ans<<endl;
continue;
}
merge(x,y+2*n),merge(x+n,y),merge(x+2*n,y+n);
}
}
cout<<ans;
return 0;
}