题目地址:poj1182
说实话,看了这篇文章之后才理解的食物链总结
代码基本也是照抄的,这里说说理解。
显然是用并查集,但这里不是简单地使用并查集,好像大家都称之为带权并查集。
首先看看说法错误,有3种:
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
假如X,Y不是第2)第3)种错误的关系,则有两种情况:
①X,Y由此前给出的说法可以判断他们的关系。
②X,Y不能由此前给出的说法判断答案判断他们的关系。
显然若X,Y有关系,那么用集合的思想就是它们属于同一个集合。没有关系就是不属于同一个集合。
第②种情况中,若不能由此前的关系判断他们的关系,那么这句话肯定是对的(题意),并且应该作为一种新的关系加入到集合中或者创建一个新的集合。
至于第①种情况,因为可以由之前的说法判断他们的关系,因此他们可以看作是存在直接或间接的关系的,属于同一个集合。
那么如何判断一个集合中两者的关系呢?使用大神所说的向量思维。
假设有x,y和他们各自的父亲tx,ty。其中知道关系
tx ty
| |
x ----- y
即tx(父)->x(子),ty->y,x->y的关系,并且将这些关系数量化 rank = 0/1/2,分别表示父子同类,父吃子,子吃父。
称这些数为偏移量。如tx->x则称tx到x的偏移量(x->tx的偏移量就是tx->x的偏移量的相反数)
因为tx到ty存在一条路径,那么必然可以通过这些数量关系求出tx到ty直接偏移量。(别问我为什么知道)
tx ----- ty
| |
x ----- y
tx->ty =tx->x + x->y + y->ty
tx->ty = tx->x + x->y - ty->y
好的上面一大段文字说了什么呢?大概就是说X,Y的关系可以通过其他已知的而且和X,Y有关的关系得出 *A。
设delta[i]为i和i的父亲,设为fa[i]的关系。即tx->x = delta[x]...
由上面填充了颜色的公式就可以求出他们的关系了
delta[ty] = delta[x] + delta[y] -delta[y];
由于运算过程中可能会导致最后的计算结果超出0~2的范围,可以对其加3再取模(还记得数据结构中循环队列是怎样解决假溢出的问题吗?自行百度。。)
delta[ty] = ( delta[x] + delta[y] -delta[y] +3 )%3;
因此合并两个集合的大概的操作就有
unio(int x,int y){ int tx = find(x); int ty = find(y); fa[ty] = tx; delta[ty] = ( delta[x]+delta[y]-delta[y]+3 )%3; }
ffx --\ ffx
| \ | \
fx | --> | \
| / fx x
x __/
ffx->x = ffx->fx +fx->x;
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<cstdio> const int maxn = 50000+10; int fa[maxn],r[maxn]; //fa存父节点,r存该点到父节点的关系 int N; void init(){ for(int i=1;i<=N;i++){ fa[i] = i; r[i] = 0; } } int find(int x){ if(x==fa[x]) return x; int tx = find(fa[x]); r[x] = (r[x]+r[fa[x]])%3; //使用向量思维进行路径压缩 return fa[x] = tx; } //合并的同时判断是否正确 int unio(int x,int y,int type) { if(x>N||y>N) return 1; //返回1就是假话了 if(x==y&&type==1) return 1; //xy是同类但是互相吃就是错误 int tx = find(x); int ty = find(y); if(tx==ty){ //本来x,y就是有关系的 if((r[y]-r[x]+3)%3!=type) return 1; else return 0; } fa[ty] = tx; r[ty] = (r[x]+type-r[y]+3)%3; return 0; } int main(){ int K; int d,x,y; int wrong = 0; scanf("%d%d",&N,&K); init(); for(int i=0;i<K;i++){ //cin>>d>>x>>y; scanf("%d%d%d",&d,&x,&y); if( unio(x,y,d-1) ) wrong++; } printf("%d\n",wrong); return 0; }