本来打算尝试学一下LCA的,但是发现似乎离线算法需要预备知识:并查集,于是尝试学了一下并查集。
并查集的思想:
有时候我们需要对一个集合进行操作,比如判断某个数是否属于另一个数所属于的某个集合,那么我们就可以用这样的思想:
每一个集合只存一个代表,集合中的其他元素都连接这个代表,而这个集合中除了代表连接着其他元素,其他元素不互相连接。
这样的话,速度就快很多了。比如原来我们可能需要这样:
对于一个集合大小为n的集合,时间复杂度为O(n)
而使用并查集后,复杂度为O(1)
代码:
#include<cstdio> #include<iostream> using namespace std; int data[10001]; int find(int k){ if(data[k]==k)return k; return data[k]=find(data[k]); } int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++){//n个元素 data[i]=i; } for(int i=0;i<m;i++){//m个操作 int a,b,c; cin>>a>>b>>c; if(a==1){ data[find(data[b])]=find(c);//将b的爸爸变成c的爸爸 注意,这里需要让b的最大的爸爸的爸爸变成c的最大的爸爸。在find的同时就会更新每个元素的爸爸为当前的总爸爸(return时的处理) }else if(a==2){ if(find(b)==find(c)){ cout<<"Y"<<endl; }else{ cout<<"N"<<endl; } } } }
关键的方法就是find(int k)方法。重点是对这个方法的理解,剩余的内容主要是输入的进行。对这个方法的理解可以看一下洛谷的题解。
我对find(int k)方法的理解:
第一行{ if(data[k]==k)return k; }:k的值为要查询的值,方法本身的用处是查找k的最大的父亲,那么这一行的意思就是:如果k的父亲是他自己,就返回他自己。
第二行{ return data[k]=find(data[k]); }:如果k的父亲不是k自己,就查找 k的父亲 的最大的父亲,并设置k的最大的父亲为 k的父亲 的最大的父亲。这样就可以得出,从k到k次一级的父亲都会直接变成最大的父亲的儿子。
题目(洛谷):https://www.luogu.org/problemnew/show/P3367
题解(洛谷):https://www.luogu.org/problemnew/solution/P3367