题目
很经典的并查集题目
反集做法:开三倍的数组,分别是吃x的和x吃的。
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int MAXN=200000;
int f1[MAXN];
int find(int x)//找爸+路径压缩
{
if(f1[x]==x) return x;
return f1[x]=find(f1[x]);
}
int main()
{
int n,m;
int ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=3*n;i++)
f1[i]=i;//三倍集合
for(int i=1;i<=m;i++)
{
int x,y,c;
scanf("%d%d%d",&c,&x,&y);
if(x>n||y>n||(x==y&&c==2))
{
ans++;
continue;//特判
}
int fx=find(x);
int fy=find(y);
if(c==1)
{
if(fx==fy) continue;
if(fx==find(y+n)||fx==find(y+2*n)||fy==find(x+2*n)||fy==find(x+n))
{
ans++; continue;
}
f1[find(x)]=find(y);//x的同类是y
f1[find(x+n)]=find(y+n);//x+n的同类是y+n
f1[find(x+2*n)]=find(y+2*n);//x+2n的同类是y+2n
}
else
{
if(fx==fy||fx==find(y+2*n)||fy==find(x+n))
{
ans++; continue;
}
f1[fx]=find(y+n);//fx即使吃y的动物的同类
f1[fy]=find(x+2*n);//fy即使被x吃动物的同类
f1[find(x+n)]=find(y+2*n);//x吃y,y吃z,那么z就吃x0.
}
}
printf("%d",ans);
return 0;
}
带权并查集的方法
front数组是这个点到根节点的关系,因为只有三种动物且存在x吃y,y吃z那么z就吃x的关系,所以用0表示同类,1表示x吃y,2表示y吃z。
!(图片)注意有个关键就是当我们知道x与祖先x1的关系,y与祖先y1的关系,x与y的关系时,求x1与y1的关系时,使用矢量计算:
x1->x->y->y1 计算
所以front[x1]=(-front[x]+front[y]+c-1+3)%3//防止出负数
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int f1[999999],front[999999];
int find(int x)
{
if(x==f1[x])
return f1[x];
int y=find(f1[x]);
front[x]=(front[x]+front[f1[x]])%3;//计算与根节点的距离
return f1[x]=y;
}
int main()
{
int n,m;
int ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
f1[i]=i;
for(int i=1;i<=m;i++)
{
int x,y,c;
scanf("%d%d%d",&c,&x,&y);
if(x>n||y>n||(x==y&&c==2)) //特判
{
ans++;
continue;
}
int fx=find(x);
int fy=find(y);
if(fx==fy)//共同祖先,判断真话假话。
{
if((front[x]-front[y]+3)%3!=c-1) ans++;//出现矛盾假话++
}
else//不是共同祖先,默认真话,建立关系。
{
f1[fx]=fy;
front[fx]=(-front[x]+c-1+front[y]+3)%3;//计算fx与fy的距离
}
}
printf("%d",ans);
return 0;
}