【NOI2001T1】食物链-并查集
题目大意:一个地方的动物分A,B,C三类,A吃B,B吃C,C吃A,两种动物之间要么是同类,要么就有吃与被吃的关系。按顺序给定K句话,描述的是某两种动物是同类或某种动物吃某种动物。求这些话中假话的数量(假话的定义在原题中有)。
做法:用f[i]表示i号动物所属集合的代表动物,r[i]表示i号动物与它所属集合代表动物之间的关系,为0时代表它们是同类,为1时代表i号动物吃它所属集合的代表动物,为2时反之。由于动物之间的关系刚开始是不确定的,因此我们用并查集来记录哪些动物之间的关系是可以确定的。每读入一个关系,如果关系涉及的两种动物不属于同一个集合,则表示它们之间的关系仍不能确定,则认定这句话为真话,合并两个集合。如果它们属于同一个集合,则表示它们之间的关系已经可以确定,则先求出它们之间的关系,再将它与描述中的关系比较,判断是否为假话。关键就在于怎么维护这个关系。这里主要是合并和求某两种动物之间关系的问题:合并时,如有两种动物x,y,关系为d,因为x与f[x]关系为r[x],y与f[y]关系为r[y],得出f[x]和f[y]之间关系为(r[y]+d-r[x]+3) mod 3,括号里加上3是为了防止减的结果为负数,然后再像普通并查集一样合并即可。求两种动物x,y之间关系,可以用上面的推论方法得出它们之间的关系为(r[x]-r[y]+3) mod 3。路径压缩中维护r的方法与上面的推论方法类似,不再赘述。至此,我们已经可以解决这个问题了。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
long n,k,ans=0,f[50010]={0},r[50010]={0};
long find(long x)
{
long t;
if (f[x]==x) return x;
t=f[x];
f[x]=find(f[x]);
r[x]=(r[t]+r[x])%3;
return f[x];
}
void merge(long x,long y,long len)
{
long fx=find(x),fy=find(y);
f[fx]=fy;
r[fx]=(r[y]-r[x]+3+len)%3;
}
int main()
{
scanf("%ld %ld\n",&n,&k);
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=k;i++)
{
long d,x,y;
scanf("%ld %ld %ld\n",&d,&x,&y);
if (x>n||y>n||(d==2&&x==y)) ans++;
else
{
long fx=find(x),fy=find(y);
if (fx==fy)
{
if ((r[x]-r[y]+3)%3!=d-1) ans++;
}
else merge(x,y,d-1);
}
}
printf("%ld",ans);
return 0;
}