总结一下我理解的带权并查集
Posted simaomao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了总结一下我理解的带权并查集相关的知识,希望对你有一定的参考价值。
总结一下我理解的带权并查集
与普通并查集的区别:普通的并查集仅仅记录的是集合的关系,这个关系无非是同属一个集合或者是不在一个集合,而带权并查集是记录集合内元素的关系,而这个关系被带上了一个权值表示集合内元素之间关系的区别,例如食物链这道题,权值为0表示和根节点是同类,权值为1表示吃根节点。。。
用向量法来表示带权并查集
查询是否在同一个集合,或者同一个集合内的元素如何表示他们之间的关系
?
每个元素带一个权值用rank表示
如上图,用向量法表示则是a->root+a->b=b->root,则a->b=b->root - a->root,在带权并查集里面,我们把式子内的元素用权值表示就可以了,所以我们可以通过这个式子来判断两个元素是否在同一个集合内
合并:
有两个集合
现在我们要考虑的是如何去连接这两个集合(谁合并到谁上面都无所谓)
红色虚线是为了方便用向量法推导出他们之间的关系
如图,易知
(1)a->rootb=a->b+b->rootb,则a->b=a->rootb-b->rootb
(2)a->roota+roota->rootb=a->rootb
(3)roota->rootb=a->rootb - a->roota
又由(1)可知
roota->rootb=a->rootb - a->roota=a->b + b->rootb - a->roota
根据题意替换成相应的权值就可以了,来看看题目吧
食物链 POJ - 1182
以前用过种类并查集的方法写过, https://blog.csdn.net/weixin_42488861/article/details/97621286
现在用带权并查集水一贴
大佬博客:
https://blog.csdn.net/niushuai666/article/details/6981689#commentsedit
https://www.cnblogs.com/liyinggang/p/5327055.html
看博客的时候有点不明白为什么可以分为三种情况,0为同类,1吃根节点,2被根节点吃,而且
我不明白的地方就是在计算的时候d-1永远不可能等于2呀,后来我才明白了,关系为2是在合并过程中改变的,而我们只需要用题意所给的关系带入带权并查集的向量公式就可以了(如上图所示)。。。是我想得太复杂
1 #include<iostream> 2 #include <algorithm> 3 #include <queue> 4 #include<cstring> 5 #include<stdio.h> 6 #include<cmath> 7 using namespace std; 8 int n,m; 9 struct node{ 10 int father; 11 int rank; 12 }star[3000005]; 13 int ans; 14 int Find(int x){//查询 15 //return x==father[x]? x : Find(father[x]); 16 if(x==star[x].father) return x; 17 //路径压缩,直接与祖宗节点相连,加快查询速度 18 if(x!=star[x].father){ 19 int temp=star[x].father; 20 21 //从x结点搜索到祖先结点所经过的结点都指向该祖先结点 22 star[x].father=Find(star[x].father); 23 24 star[x].rank=(star[x].rank+star[temp].rank)%3; 25 26 //在搜索祖宗节点过程中 27 //在idx前面的节点位置也会更新,所以idx的父节点的父节点。。。都要更新 28 //所以找到祖宗节点后,需要将idx原本的rank加上idx父节点的rank 29 //至于为什么是在原本idx的rank直接加上父节点的rank, 30 //因为他们两个之间的距离是不变的 31 } 32 return star[x].father; 33 } 34 int main() 35 { 36 //ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); 37 38 //while(cin>>n>>m){ 39 //cin>>n>>m; 40 scanf("%d%d",&n,&m); 41 for(int i=0;i<=n+3;i++){ 42 star[i].father=i; 43 star[i].rank=0; 44 } 45 while(m--){ 46 int op,a,b; 47 //cin>>op>>a>>b; 48 scanf("%d%d%d",&op,&a,&b); 49 /*两种明显的错误*/ 50 if(a>n||b>n){ 51 ans++; 52 continue; 53 } 54 if(op==2&&a==b){ 55 ans++; 56 continue; 57 } 58 59 int fa=Find(a); 60 int fb=Find(b); 61 62 /*精华部分*/ 63 if(fa==fb){//同一个集合 64 if((star[a].rank-star[b].rank+3)%3!=op-1)ans++; 65 } 66 else {//不是同一个集合就直接合并 67 star[fa].father=fb; 68 69 star[fa].rank=(op-1-star[a].rank+star[b].rank)%3; 70 71 } 72 73 /*---------------方法2----------------------*/ 74 /*if(fa!=fb){//不是同一个集合就合并 75 star[fa].father=fb; 76 77 star[fa].rank=(op-1-star[a].rank+star[b].rank)%3; 78 } 79 else{//在同一个集合中 80 //那么条件1的情况下a,b的根节点的rank肯定是一样的 81 if(op==1&&(star[fa].rank!=star[fb].rank)){ 82 ans++; 83 } 84 else if(op==2&&(star[a].rank-star[b].rank+3)%3!=op-1)){ 85 ans++; 86 } 87 } */ 88 /*---------------方法2----------------------*/ 89 } 90 cout<<ans<<endl; 91 //} 92 93 94 95 return 0; 96 } 97
Parity game POJ - 1733
给定一串长为n的数,只由0和1组成。给定m次询问,在第x至第y位(注意是闭区间[x,y])中1的个数是奇数或是偶数。问从哪一次开始,下一次的回答就与之前的矛盾。
思路:带权并查集+离散化,用map去做就不需要离散化了,map帮你搞定,需要注意的是这个做法有点特殊,在Find函数需要改动,father不存在时,说明父节点是他本身,直接返回当前节点,相当于当前的父节点是它本身
奇偶性的话,0代表偶数,1代表奇数,直接带进公式就可以了
ps:注意是闭区间,如:给定区间为[a,b],合并时需要a--或者b++
为了维护区间加法,我们必须要维护一个左开右闭区间,(a,b]+(b,c]=(a,c]
1 ? 2 #include<iostream> 3 #include <algorithm> 4 #include <queue> 5 #include<cstring> 6 #include<stdio.h> 7 #include<cmath> 8 #include<map> 9 using namespace std; 10 //有几个集合就有几张桌子 11 12 typedef long long ll;ll n,m; 13 14 map<int,int>father; 15 map<int,int>rank; 16 17 ll ans; 18 int Find(int x){//查询 19 //return x==father[x]? x : Find(father[x]); 20 if(!father[x]) return x;//父节点初始化为0,一旦不为0就是他的祖宗节点了 21 22 int temp=father[x]; 23 24 father[x]=Find(father[x]); 25 26 rank[x]=(rank[x]+rank[temp])%2; 27 return father[x];//返回根节点 28 } 29 int main() 30 { 31 ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); 32 33 //while(){ 34 int flag=0; 35 cin>>n>>m; 36 37 father.clear(); 38 rank.clear(); 39 for(int i=1;i<=m;++i){ 40 int a,b,c; 41 string s; 42 cin>>a>>b>>s; 43 if(s=="even")c=0; 44 else c=1; 45 a--; 46 int fa=Find(a); 47 int fb=Find(b); 48 /*---------------*/ 49 if(fa==fb){ 50 //if(star[a].rank+c!=star[b].rank) 51 if(flag==0&&(rank[a]-rank[b]+2)%2!=c){ 52 ans=i; 53 flag=1; 54 } 55 56 } 57 else{//合并组织 58 if(flag)continue; 59 father[fa]=fb; 60 rank[fa]=(rank[b]+c-rank[a]+2)%2; 61 } 62 63 //if(combine(a-1,b,c))ans++; 64 65 } 66 // } 67 ans--; 68 //ans-=2; 69 if(flag) 70 cout<<ans<<endl; 71 else cout<<m<<endl; 72 73 74 75 return 0; 76 } 77 78 79 ?
以上是关于总结一下我理解的带权并查集的主要内容,如果未能解决你的问题,请参考以下文章