专题训练之并查集(带权并查集、可持久化并查集)
推荐一个博客:https://blog.csdn.net/tribleave/article/details/72878239 并查集和带权并查集
带权并查集:在并查集的基础上,对其中的每一个元素赋有某些值。在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系。
带权并查集习题:
1.(POJ1182)http://poj.org/problem?id=1182
推荐几个讲解详细的博客:https://blog.csdn.net/niushuai666/article/details/6981689 从向量的角度看问题
分析:难点在于:A.路径压缩时的关系域的更新
B.条件判断时,若两个点的根节点不同则合并;若根节点相同则按照当前给定关系与原本的关系进行判断。
总的来说就是两点之间的偏移量大小的确定
注意:改组只有一组,多组读入会WA
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=5e4+10; 6 struct node{ 7 int pre; 8 int realtion; 9 }f[maxn]; 10 11 int find(int x) 12 { 13 if ( !f[x].pre ) return x; 14 int tmp=f[x].pre; 15 f[x].pre=find(tmp); 16 f[x].realtion=(f[x].realtion+f[tmp].realtion)%3; 17 return f[x].pre; 18 } 19 20 int main() 21 { 22 int n,k,op,a,b,rt1,rt2,ans; 23 scanf("%d%d",&n,&k); 24 ans=0; 25 memset(f,0,sizeof(f)); 26 for ( int i=1;i<=k;i++ ) 27 { 28 scanf("%d%d%d",&op,&a,&b); 29 if ( a>n || b>n ) 30 { 31 ans++; 32 continue; 33 } 34 if ( op==2 && a==b ) 35 { 36 ans++; 37 continue; 38 } 39 rt1=find(a); 40 rt2=find(b); 41 if ( rt1!=rt2 ) 42 { 43 f[rt2].pre=rt1; 44 f[rt2].realtion=(f[a].realtion+op-1+3-f[b].realtion)%3; 45 } 46 else 47 { 48 if ( op==1 && f[a].realtion!=f[b].realtion ) 49 { 50 ans++; 51 continue; 52 } 53 if ( op==2 && ((3-f[a].realtion+f[b].realtion)%3!=1) ) 54 { 55 ans++; 56 continue; 57 } 58 } 59 } 60 printf("%d\n",ans); 61 return 0; 62 }
2.(POJ1988)http://poj.org/problem?id=1988
题意:初始时有N堆砖块,每堆有一个(编号为1-N)。现在有q个操作,
操作分2种:M X Y 将含编号X的那堆放置在编号为Y堆的上方
C X 含编号为X的砖块下方有多少个砖块
分析:设置数组d[i]表示编号为d的下方有多少块砖(同时也能表示编号为i的点到其所在树的树根的距离),d[i]初始化为0。还需要设置数组num[i]表示当i为一堆最下面的那个砖块(所在树的数根)总共包含多少个点。对于两种操作:查找时i到数根的距离等于i到原来数根的距离+原来数根到新树根的记录。合并时,原来的数根到新数根的距离为新树根包含点的个数,新树根点的个数加上原来数根点的个数。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=3e4+10; 6 int f[maxn],d[maxn],num[maxn]; 7 8 9 int find(int x) 10 { 11 if ( f[x]==-1 ) return x; 12 int tmp=find(f[x]); 13 if ( tmp==f[x] ) return f[x]; 14 else 15 { 16 d[x]+=d[f[x]]; 17 return f[x]=tmp; 18 } 19 } 20 21 int main() 22 { 23 int n,i,j,x,y,q; 24 char s[10]; 25 while ( scanf("%d",&q)!=EOF ) 26 { 27 for ( i=1;i<=30000;i++ ) 28 { 29 f[i]=-1; 30 d[i]=0; 31 num[i]=1; 32 } 33 while ( q-- ) 34 { 35 scanf("%s",s); 36 if ( s[0]=='M' ) 37 { 38 scanf("%d%d",&x,&y); 39 int fx=find(x); 40 int fy=find(y); 41 if ( fx!=fy ) 42 { 43 f[fx]=fy; 44 d[fx]=num[fy]; 45 num[fy]+=num[fx]; 46 } 47 } 48 else 49 { 50 scanf("%d",&x); 51 int fx=find(x); 52 printf("%d\n",d[x]); 53 } 54 } 55 } 56 return 0; 57 }
3.(HDOJ3047)http://acm.hdu.edu.cn/showproblem.php?pid=3047
题意:有n个人坐在zjnu体育馆里面,然后给出m个他们之间的距离, A B X, 代表B的座位比A多X. 然后求出这m个关系之间有多少个错误,所谓错误就是当前这个关系与之前的有冲突。
分析:大致用POJ1182,每个点的relation为其于其父节点的距离(也可以说是父节点到其的偏移量),为正代表父节点的座位号比该节点大,为负代表父节点的座位号比该节点小。每次更新时注意偏移量的变化(特别注意正负),每次访问若为同一个点则判断A到B的偏移量是否为x即可
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=5e4+10; 6 struct node{ 7 int pre; 8 int relation; 9 }f[maxn]; 10 11 int find(int x) 12 { 13 if ( f[x].pre==-1 ) return x; 14 int tmp=f[x].pre; 15 f[x].pre=find(tmp); 16 f[x].relation=(f[x].relation+f[tmp].relation); 17 return f[x].pre; 18 } 19 20 21 int main() 22 { 23 int n,m,i,j,k,x,y,d,fx,fy,num,ans; 24 while ( scanf("%d%d",&n,&m)!=EOF ) 25 { 26 for ( i=1;i<=n;i++ ) 27 { 28 f[i].pre=-1; 29 f[i].relation=0; 30 } 31 ans=0; 32 while ( m-- ) 33 { 34 scanf("%d%d%d",&x,&y,&d); 35 fx=find(x); 36 fy=find(y); 37 if ( fx==fy ) 38 { 39 if ( -f[x].relation+d+f[y].relation!=0 ) 40 { 41 ans++; 42 continue; 43 } 44 } 45 else 46 { 47 f[fx].pre=fy; 48 f[fx].relation=-f[x].relation+d+f[y].relation; 49 } 50 } 51 printf("%d\n",ans); 52 } 53 return 0; 54 }