POJ_1182
这个题目在看了别人的解法之后不由地感叹原来并查集也可以这么用。
首先,由于A、B、C三种生物之间存在着食物链的关系,如果单单用并查集p[]是解决不了问题了,这时还需要一个附加的数组r[]来表示x和p[x]的关系,我选用的关系为0代表x和p[x]同类,1代表x吃p[x],2代表p[x]吃x。
之后难点一共有下面几处:
①并查集的查找和路径压缩。我们在压缩路径的时候必然要去找x和find(x)之间的关系,而x和find(x)有可能隔了很多层,因此我们需要从find(x)开始自底向上依次更新r[],有点类似深搜的回溯。在更新关系的时候运用到了类似向量的思考模式,比如我想知道a与c的关系,而现在已知a与b的关系,b与c的关系,那么a与c的关系就应该是a与b的关系与b与c的关系的“和”,回到这道题目,就是说在回溯的时候已知子节点和父节点的关系,以及父节点和根结点的关系,然后把两个关系“相加”就得到了子节点与根节点的关系。
②并查集的合并操作。我们比较容易理解,如果x和y不属于一个并查集,那么这句话就一定是真话,但难点在于我们如何把这两个并查集进行合并,也就是说如何去找到find(x)与find(y)之间的关系。这里同样要运用类似向量的思想,即我们现在已知x与find(x)的关系,y与find(y)的关系,以及x与y的关系,3个量进行运算之后自然就可以得到find(x)与find(y)之间的关系。
③对矛盾的判断。我们比较容易知道,只有当find(x)==find(y)的时候才可能出现矛盾,这时我们如何知道这句话是否会和前面的话矛盾呢?其实也就是去判断当前已知的x与y的关系是否与前面的关系矛盾,而我们又本来就知道x与find(x)的关系以及y与find(y)之间的关系,自然可以推导出正确x与y之间的关系,那么便只要把输入的关系和推理的关系进行比较就可以知道输入的关系是否成立了。
#include<string.h>
#include<stdio.h>
int p[50010],r[50010],N,K;
int find(int x)
{
int tx;
if(p[x]==x)
return x;
tx=find(p[x]);
r[x]=(r[p[x]]+r[x])%3;
return p[x]=tx;
}
int check(int D,int x,int y)
{
int i,j,tx,ty;
if(x>N||y>N)
return 0;
if(D==2&&x==y)
return 0;
tx=find(x);
ty=find(y);
if(tx==ty)
{
if((r[y]-r[x]+D+2)%3==0)
return 1;
return 0;
}
else
{
p[tx]=ty;
r[tx]=(r[y]-r[x]+D+2)%3;
return 1;
}
}
int main()
{
int i,j,k,num,x,y,D;
while(scanf("%d%d",&N,&K)==2)
{
for(i=1;i<=N;i++)
{
p[i]=i;
r[i]=0;
}
num=0;
for(i=0;i<K;i++)
{
scanf("%d%d%d",&D,&x,&y);
if(!check(D,x,y))
num++;
}
printf("%d\n",num);
}
return 0;
}