并查集所维护的是一种所属关系,通过并查集最快可以在O(1)的时间里查询到两者的关系,但有的时候,关系并不是特别简单。我们要维护除了关系外的一些别的东西。比如关系的类型,比如到根的距离等等。
例题一:食物链
动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B
吃 C,C 吃 A。现有 N 个动物,以 1 - N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道
它到底是哪一种。
有人用两种说法对这 N 个动物所构成的食物链关系进行描述:
第一种说法是“1 X Y”,表示 X 和 Y 是同类。
第二种说法是“2 X Y”,表示 X 吃 Y 。
此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真
的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
• 当前的话与前面的某些真的话冲突,就是假话
• 当前的话中 X 或 Y 比 N 大,就是假话
• 当前的话表示 X 吃 X,就是假话
你的任务是根据给定的 N 和 K 句话,输出假话的总数。
输入格式:
第一行两个整数,N,K,表示有 N 个动物,K 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出格式:一行,一个整数,表示假话的总数。
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
3
分析:一开始做这个题的时候,一直使用的是并查集补集的方法,与关押罪犯相似,开三倍的空间 1-N表示同类的,N+1-N*2表示他吃的,N*2+1-N*3表示吃他的。但是所有的并查集的扩展域的题目都是以这个题作为例题,因此就用这个题作为学习的例题。
与补集不同,这一种做法,是有祖先,但是还同时记录了这一个点和祖先之间的关系,在路径压缩维护时,同时会根据他的父节点来更新到祖先的关系。因此,在更新的时候,要提前记录父节点,然后根据父节点的关系,更新祖先的关系。
#include<cstdio> #include<iostream> using namespace std; struct nodo { int pa;//表示祖先是谁 int re;//表示到祖先的关系 0:同类、1:被祖先吃、2:吃祖先 }; nodo fa[50010]; int sumn,n,k; int find(int ui){ if(ui==fa[ui].pa) return ui; int last=fa[ui].pa; int grand=find(fa[ui].pa); if(fa[ui].re==0){ if(fa[last].re==0) {fa[ui].re=0;return fa[ui].pa=grand;} if(fa[last].re==1) {fa[ui].re=1;return fa[ui].pa=grand;} if(fa[last].re==2) {fa[ui].re=2;return fa[ui].pa=grand;} } if(fa[ui].re==1){ if(fa[last].re==0) {fa[ui].re=1;return fa[ui].pa=grand;} if(fa[last].re==1) {fa[ui].re=2;return fa[ui].pa=grand;} if(fa[last].re==2) {fa[ui].re=0;return fa[ui].pa=grand;} } if(fa[ui].re==2){ if(fa[last].re==0) {fa[ui].re=2;return fa[ui].pa=grand;} if(fa[last].re==1) {fa[ui].re=0;return fa[ui].pa=grand;} if(fa[last].re==2) {fa[ui].re=1;return fa[ui].pa=grand;} } } void add(int xi,int yi,int wi){ int f1=find(xi); int f2=find(yi); fa[f1].pa=f2; if(wi==1){ if(fa[xi].re==0){ if(fa[yi].re==0) {fa[f1].re=0;return ;} if(fa[yi].re==1) {fa[f1].re=1;return ;} if(fa[yi].re==2) {fa[f1].re=2;return ;} } if(fa[xi].re==1){ if(fa[yi].re==0) {fa[f1].re=2;return ;} if(fa[yi].re==1) {fa[f1].re=0;return ;} if(fa[yi].re==2) {fa[f1].re=1;return ;} } if(fa[xi].re==2){ if(fa[yi].re==0) {fa[f1].re=1;return ;} if(fa[yi].re==1) {fa[f1].re=2;return ;} if(fa[yi].re==2) {fa[f1].re=0;return ;} } } if(wi==2){ if(fa[xi].re==0){ if(fa[yi].re==0) {fa[f1].re=2;return ;} if(fa[yi].re==1) {fa[f1].re=0;return ;} if(fa[yi].re==2) {fa[f1].re=1;return ;} } if(fa[xi].re==1){ if(fa[yi].re==0) {fa[f1].re=1;return ;} if(fa[yi].re==1) {fa[f1].re=2;return ;} if(fa[yi].re==2) {fa[f1].re=0;return ;} } if(fa[xi].re==2){ if(fa[yi].re==0) {fa[f1].re=0;return ;} if(fa[yi].re==1) {fa[f1].re=1;return ;} if(fa[yi].re==2) {fa[f1].re=2;return ;} } } } int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;i++){ fa[i].pa=i; fa[i].re=0; } while(k--){ int ti,xi,yi; scanf("%d%d%d",&ti,&xi,&yi); if(xi>n||yi>n){ sumn++; continue; } if(ti==1){ int f1=find(xi); int f2=find(yi); if(f1==f2){ if(fa[xi].re==fa[yi].re){ continue; } else{ sumn++; } } else{ add(xi,yi,1); } } if(ti==2){ int f1=find(xi); int f2=find(yi); if(f1==f2){ if(fa[xi].re==1&&fa[yi].re==2) continue; if(fa[xi].re==0&&fa[yi].re==1) continue; if(fa[xi].re==2&&fa[yi].re==0) continue; sumn++; } else{ add(xi,yi,2); } } } printf("%d",sumn); }
例题二:统计错误答案
题意:给出区间[1,n],下面有m组数据,l r v区间[l,r]之和为v,每输入一组数据,判断此组条件是否与前面冲突 ,最后输出与前面冲突的数据的个数.。
分析:这个题的思路也是使用扩展域,设一个虚点作为根节点。【L,R】的值为4,就是相当于L到根的长度,比R到根的长度多4。类似于向量的做法,关键点是查询时要检查【L-1到R】的值。每次合并的时候,同时维护该点到根的长度。最后修改L的祖先,L到原来祖先的长度+原来祖先到新祖先的长度-R到新祖先的长度==[L-R]的值,以此来作为更新的依据。
#include<cstdio> #include<iostream> using namespace std; struct nodo { int pr; int su; }; nodo fa[200010]; int n,m,sumn; int find(int ui){ if(fa[ui].pr==ui) return ui; int past=fa[ui].pr; int grand=find(fa[ui].pr); fa[ui].su+=fa[past].su; return fa[ui].pr=grand; } int main(){ while(scanf("%d%d",&n,&m)!=EOF){ sumn=0; for(int i=0;i<=n;i++){ fa[i].pr=i; fa[i].su=0; } while(m--){ int xi,yi,si; scanf("%d%d%d",&xi,&yi,&si); xi--; int f1=find(xi); int f2=find(yi); if(f1==f2){ if(fa[xi].su-fa[yi].su==si) continue; sumn++; } else{ fa[f1].pr=f2; fa[f1].su=-fa[xi].su+fa[yi].su+si;//fa[yi]-fa[xi]==si } } printf("%d\n",sumn); } return 0; }
参考博客:
https://www.cnblogs.com/qq136155330/p/9395394.html
https://www.cnblogs.com/liyinggang/p/5327055.htm