先介绍一下并查集的基本功能
基础的并查集主要有两种.
第一种是普通的并查集,主要用来处理无权的相对关系,比如说A和B是一类人,B和C是一类人,那么A和C也是一类人.
第二种是带权的并查集.种类并查集也是其中的一种.具体下面会介绍.
1.无权并查集
先引入一个问题.会给N个关系.A B代表A和B是一类东西.且具有传递性.
然后给Q个询问.问A B 是否是同一类东西.如果要O(1)的询问,我们可以建立一个数组.下标代表数值,数组的值代表种类. 这种方式询问确实是O(1)的,但修改需要O(N)的.不够优秀.我们需要引入一种新的数据结构来解决这个问题.
和上面的解法类似.不过我们引入一个新的概念.让每个数值都有一个代表元.数组名字记为fa.
一开始全部数值的代表元都是它自己.之后如果A B 是同类人,就让fa[A]=fa[B]这里的fa不是指数组,而是指他的真正代表元.因为在多次合并之后fa[A]不一定是A的代表元了.可能是fa[fa[A]].这是一个套娃的问题.所以.递归求解.
如果看文字看不懂,就画图理解,无论是数据结构还是算法.画图都是一个很直观的理解方法.
在这里.顶部的就是下面几个节点的代表元.要找到真正的代表元.可以用一个简单的get函数来表示.return x == fa[x]?x:get(fa[x]); 这个写法不够优秀.凡是树的问题就要考虑退化问题.如果退化成为一条链的时候.查找的效率还是O(n)的.所以要压缩路径.我们可以在一次递归后直接把fa[x]指向它的跟节点.
非递归版本可以更好的理解它.(效率也更高).
int get(int x){
int k,j,r;
r = x;
while(r != fa[r]) r = fa[r];
k = x;
while(k != r){
j = fa[k];
fa[k] = r;
k = j;
}
return r;
}
A B合并就很简单了.直接上代码了.(其实是有按秩优化的方法,但是其实影响不是很大.压缩路径之后效率均摊下来是够用的)
int unite(int x,int y){
x = get(x), y = get(y);
fa[x] = y;
}
一道很基础的例题(其实不是特别特别基础.建议配合离散化食用.)
补充一下离散化的知识点吧.所谓离散化,就是忽略具体数值,只记录相对的大小.比如说100 200 300 400 500 离散化之后可以是1 2 3 4 5.这个不会改变他本身在数组中的位置,但是可以缩小它本身的值.可以理解为给原先数组中的每一个元素都取了一个新名字,这个新名字就是离散化数组里面该元素对应的下标.
前面提到了并查集fa数组的下标是它的数值.在这题里数值是有1≤i,j≤1000000000的.太大了.数组开不下
但是1≤n≤1000000也就是说就算所有的i j都不同.最多也才2000000个数字.离散化之后下标就是0-1999999.这样子数组就开的下了.而离散化也是有模板的.
我一般是用vector存原先的数值,然后排序去重拿到处理好的数组后用lower_bound来得到新数组里面的值.
ps:这几个函数不熟悉的话建议百度一下.
离散化模板代码:
void LS(vector<int> &x){
//x里面储存着原先所有的信息
sort(x.begin(),x.end());
x.erase(unique(x.begin(),x.end()),x.end());
}
int getnewpos(vector<int> x,int xx){
return lower_bound(x.begin(),x.end(),xx)-x.begin();
}
本题代码
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define pll pair<long long,long long>
#define pdd pair<double,double>
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
const int mod = 9901;
LL gcd(LL a,LL b){
return b == 0 ? a : gcd(b,a%b);}
int fa[N];
vector<int> all;
int get(int x){
int k,j,r;
r = x;
while(r != fa[r]) r = fa[r];
k = x;
while(k != r){
j = fa[k];
fa[k] = r;
k = j;
}
return r;
}
int getpos(int x){
return lower_bound(ALL(all),x)-all.begin();
}
int main(){
int t;
cin >> t;
while(t--){
int n;
cin >> n;
all.clear();
vector<pii> v1,v2;
fir(i,1,n){
int x,y,q;
cin >> x >> y >> q;
all.pb(x);
all.pb(y);
if(q) v1.pb(make_pair(x,y));
else v2.pb(make_pair(x,y));
}
sort(ALL(all));
all.erase(unique(ALL(all)),all.end());
fir(i,0,(int)all.size()-1){
fa[i] = i;
}
fir(i,0,(int)v1.size()-1){
int x = getpos(v1[i].ft), y = getpos(v1[i].sd);
int fx = get(x), fy = get(y);
fa[fx] = fy;
}
bool f = 1;
fir(i,0,(int)v2.size()-1){
int x = getpos(v2[i].ft), y = getpos(v2[i].sd);
int fx = get(x), fy = get(y);
if(fx == fy){
f = 0;
break;
}
}
if(f) puts("YES");
else puts("NO");
}
return 0;
}
2.加深对并查集的理解
引入一道例题来彻底搞懂并查集.
银河英雄传说
先考虑怎么算A B相隔了多少个飞艇.先算出A和根节点的距离d1,再算出B和跟节点的距离d2.那么abs(d1-d2)-1就是相隔的数量.我们可以用一个d[i]数组来维护这个信息.同时也要更改压缩路径的写法,只要先求出i到跟节点的d 然后每次减掉d[i]就可以了
代码
int get(int x){
int k,j,r;
r = x;
int tmp = 0;
while(r != fa[r]){
tmp += d[r];
r = fa[r];
}
k = x;
while(k != r){
j = fa[k];
fa[k] = r;
int cur = d[k];
d[k] = tmp;
tmp -= cur