食物链总结
今天做了经典的食物链,在总结网上其它做法后,小结如下:
题意:一共就三种动物,如果A吃B,B吃C==》C吃A;
A吃B,A吃C==》B、C为同类
A被B吃,A被C吃==》B、C为同类
用并查集来做:
两种动物之间的关系通过于根节点的相对关系得出,所以关键是路径压缩与合并两个集合时的动物与根节点相对关系的变化,其实也可认为是一个问题,因为路径压缩中的变化其实是合并集合产生的子问题。
用delta【i】来表示i和i的父节点的关系,rank[i]=0/1/2分别表示 i 与父亲是同类、被父亲吃、吃父亲。
先讲合并操作:
设tx为x的父亲,ty是y的父亲,所以delta[x]表示x和tx的关系,delta[y]表示y与ty的关系,现在,合并操作要将ty的父亲置为tx,所以delta[ty]的值就要发生相应的改变,即产生了新的ty与tx的关系;
那么,如何求这个关系呢?有两种方法:第一种,可以通过实际数据推出来,
tx ty
| |
x ~ y
知道了tx与x的关系,x与y的关系,y与ty的关系,tx与ty的关系自然就推出来了
type表示x与y的关系0为同类,1为x吃y
type | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | ||||||||||
delta[x] | 0 | 0 | 0 | 1 | 0 | 1 | 2 | 2 | 1 | ||||||||||
delta[y] | 0 | 1 |
2 |
2 | 2 | 2 | 1 | 2 | 1 | ||||||||||
delta[ty] | 0 | 2 | 1 | 2 | 2 | 0 | 2 | 1 | 1 |
所以有
void unio{
fa[ty]=tx;
delta[ty]=(delta[x]-delta[y]+type+3)%3;
}
仔细再想想,tx-x 、x-y、y-ty,是不是很像向量形式,于是便有了一般化的结论:来自北大discuss
tx ty
| |
x ~ y
对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,
因为并查集中的元素均是有联系的,否则也不会被合并到当前集合中。那么我们
就把这2个元素之间的关系量转化为一个偏移量,以食物链的关系而言,不妨假设
x->y 偏移量0时 x和y同类
x->y 偏移量1时 x吃y
x->y 偏移量2时 x被y吃,也就是y吃x
有了这些基础,我们就可以在并查集中完成任意两个元素之间的关系转换了。
不妨继续假设,x的当前集合根节点tx,y的当前集合根节点ty,x->y的偏移值为d-1(题中给出的询问已知条件)
(1)如果tx和ty不相同,那么我们把ty合并到tx上,并且更新deltx[ty]值(注意:deltx[i]表示i的当前集合根节点到i的偏移量!!!!)
此时 tx->ty = tx->x + x->y + y->ty,可能这一步就是所谓向量思维模式吧
上式进一步转化为:tx->ty = (deltx[x]+d-1+3-deltx[y])%3 = deltx[ty],(模3是保证偏移量取值始终在[0,2]间)
(2)如果tx和ty相同,那么我们就验证x->y之间的偏移量是否与题中给出的d-1一致
此时 x->y = x->tx + tx->y = x->tx + ty->y,
上式进一步转化为:x->y = (3-deltx[x]+deltx[y])%3,
若一致则为真,否则为假。
牛的想法啊!
凡人就模仿着学习啦~哈哈
接下来把这个想法再运用到路径压缩中:
ffx
| \
fx \
| /
| /
x
路径压缩过程中会将fx的父亲变为x的父亲,所以要改变相对关系,即偏移量。
ffx->fx+fx->x=ffx->x;
转换成:delta[x]=(delta[fx]+delta[x])%3;
即
int find(int x){
if(x==fa[x]) return x;
int tx=find(fa[x]);
delta[x]=(delta[x]+delta[fa[x]])%3;
return fa[x]=tx;
}
完整代码如下
#include<stdio.h>
#include<string.h>
int delta[50005],fa[50005];
int n,k;
void init(){
for(int i=1;i<=n;i++){
delta[i]=0;
fa[i]=i;
}
}
int find(int x){
if(x==fa[x]) return x;
int tx=find(fa[x]);
delta[x]=(delta[x]+delta[fa[x]])%3;//关键
return fa[x]=tx;
}
int unio(int x,int y,int type){
if(x>n||y>n) return 1;
if(type==1&&x==y) return 1;
int tx=find(x);
int ty=find(y);
if(tx==ty){
if((delta[y]-delta[x]+3)%3!=type)//也用向量的思维思考,两种动物都在集合里了,判断一下是否与输入的话相一致
//x->y=x->tx+ty->y,因为tx=ty,即根节点相同,所以x与y的关系表示为(-delta[x]+delta[y]+3)%3,与type比较即可
return 1;
else return 0;
}
else {
fa[ty]=tx;
delta[ty]=(delta[x]-delta[y]+type+3)%3;//关键
return 0;
}
}
int main(){
int type;
int i,wrong,x,y;
while(scanf("%d%d",&n,&k)==2){
wrong=0;
init();
for(i=1;i<=k;i++){
scanf("%d%d%d",&type,&x,&y);
if(unio(x,y,type-1))
wrong++;
}
printf("%d\n",wrong);
}
return 0;
}