#include <bits/stdc++.h>
using namespace std;
class DSU {
public:
DSU(int _n) {
init(_n);
}
/*
查找该数的根节点,实现方式为 先查找该数的父节点,如果父节点
等于节点本身,那么该数就为根节点。
路径压缩: 由于每次查找父节点时会遍历整个路径长度,时间复杂度为o(q * n);
所以我们可以通过将每个数的父节点直接连到根节点的方法(压缩路径)降低复杂度。
*/
int find(int x) {
// if(fa[x] == x) return x;
// else return fa[x] = find(fa[x]) ;
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
/*
merge 合并两个不在同一集合的数,即将一个数的根节点放在另一个数的根节点下。
同时要更新size,只需更新根节点大小,其余不需要管,因为不会再访问到了。
*/
void merge(int x,int y) {
int fx = find(x),fy = find(y);
if(fx == fy) return;
//fa[fx] = fy;
if(sz[fx] <= sz[fy]) {
fa[fx] = fy;
sz[fy] += sz[fx];
} else {
fa[fy] = fx;
sz[fx] += sz[fy];
}
}
bool isSame(int x,int y) {
return find(x) == find(y);
}
int sze(int x) {
return sz[find(x)];
}
private:
vector<int> fa,sz;
//初始化方式
/*
方法一:
resize
函数原型:
void std::vector<T>::resize(size_t x) 接受一个size_t参数(即无符号整数,跟随系统)
即将容器大小设置为 x
用法:
resize -> fa.resize(_n + 1);(重置数组大小,数值默认为0)
assign
函数原型:
void std::vector<T>::assign(size_t x, T y)接收两个size_t,T y参数,将容器大小设置为 x,值设置为 y。
用法: sz.assign(_n + 1,1);
iota
函数原型:
void std::iota(std::vector<T>::iterator_begin, std::vector<T>::iterator_end, T x)
从begin到end的前一个位置初始化为从x开始递增的排列。
方法二:
for(int i = 0;i < _n;i++) {
fa[i] = i;
sz[i] = 1;
}
*/
// std::size_t n;for (int i = 0; i < (int)a.size(); i++)
void init(int _n) {
fa.resize(_n + 5);
iota(fa.begin(),fa.end(),0);
sz.assign(_n + 5,1);
}
};
void solve() {
int n,q;cin >> n >> q;
DSU dsu(n);
while(q--) {
int op;cin >> op;
if(op == 1) {
int a,b;cin >> a >> b;
dsu.merge(a,b);
}
if(op == 2) {
int a,b;cin >> a >> b;
if(dsu.isSame(a,b)) {
cout << "YES\n";
}
else cout << "NO\n";
}
if(op == 3) {
int x;cin >> x;
cout << dsu.sze(x) << "\n";
}
}
}
int main() {
cin.tie(0) -> sync_with_stdio(0);
solve();
return 0;
}