Problem:
Problem D: Graph Construction
n
匹のうさぎがいて, 0 番から
n
− 1 番の番号がついた小屋に1 匹ずつ住んでいる.
あるとき, 秘密の組織によって地下通路の建設工事が行われる, という情報がうさぎたちのもとへ入った. 地下通路を使えば, うさぎたちは他のうさぎたちの小屋へ遊びに行けるようになって嬉しい.
通路は両方向に進むことができ, また通路同士は交わらない. 諸事情により, 一度建設された通路が破壊されてしまうこともある. 通路の建設や破壊の工事は1 本ずつ行われ, 各工事中はうさぎは自分の小屋に留まっているものとする.
うさぎたちは, 工事のいくつかの段階において, あるうさぎとあるうさぎが遊べるかどうかを事前に知りたい. うさぎたちは仲良しなので, 遊びに行くときに他のうさぎの小屋を自由に通ることができる. プログラミングが好きなうさぎたちは, 昔似たような問題を解いたので簡単だろうと思って挑戦し出したが, なかなか効率の良いプログラムが書けない. うさぎの代わりにこの問題を解け.
Input
入力の一行目には
n
と
k
がスペース区切りで与えられる。2 ≤
n
≤ 40 000, 1 ≤
k
≤ 40 000
つづく
k
行には工事情報と質問が合わせて, 時間順に与えられる.
“1
u v
” — 小屋
u
と
v
を結ぶ通路が建設される. 小屋
u
と
v
を結ぶ通路がないときにのみ現れる.
“2
u v
” — 小屋
u
と
v
を結ぶ通路が破壊される. 小屋
u
と
v
を結ぶ通路があるときにのみ現れる.
“3
u v
” — 小屋
u
と
v
のうさぎが遊べるかどうか判定せよ.
0 ≤
u
<
v
<
n
Output
入力に現れる各質問について, 遊べるなら”YES”を, そうでないなら”NO”を1 行ずつ出力せよ.
Sample Input 1
4 10
1 0 1
1 0 2
3 1 2
2 0 1
1 2 3
3 0 1
1 0 1
2 0 2
1 1 3
3 0 2
Sample Output 1
YES
NO
YES

Submitted solutions:

code_snippet: // competitive-verifier: PROBLEM
#include <iostream>
#include <utility>
#include <vector>
#include <algorithm>
#include <bit>
#include <cassert>
#include <map>
#include <stack>
/**
 * @brief Undo可能Union-Find
 * @details Implement (union by size)
 *
 * @see https://ei1333.github.io/luzhiled/snippets/structure/union-find.html
 */
struct undo_union_find {
    undo_union_find() : data(), history() {}
    undo_union_find(int _n) : data(_n, -1), history() {}
    int root(int x) const { return data[x] < 0 ? x : root(data[x]); }
    int get_root(int k) const { return root(k); }
    bool is_root(int k) const { return data[k] < 0; }
    bool same(int x, int y) const { return root(x) == root(y); }
    bool is_same(int x, int y) const { return same(x, y); }
    int size(int k) const { return -data[root(k)]; }
    int get_size(int k) const { return size(k); }
    bool unite(int x, int y) {
        x = root(x), y = root(y);
        history.emplace(x, data[x]);
        history.emplace(y, data[y]);
        if (x == y) return false;
        if (data[x] > data[y]) std::swap(x, y);
        data[x] += data[y];
        data[y] = x;
        return true;
    }
    void undo() {
        data[history.top().first] = history.top().second;
        history.pop();
        data[history.top().first] = history.top().second;
        history.pop();
    }
    int snapshot() const { return history.size(); }
    void rollback(int x = 0) {
        while ((int)(history.size()) > x) undo();
    }
  private:
    std::vector<int> data;
    std::stack<std::pair<int, int>> history;
};
/// @brief オフライン動的連結性
struct offline_dynamic_connectivity {
    offline_dynamic_connectivity(int n, int q) : _q(q), uf(n) {
        _size = std::bit_ceil<unsigned>(_q);
        _log = std::countr_zero<unsigned>(_size);
        seg = std::vector(_size << 1, std::vector<std::pair<int, int>>());
    }
    void add(int t, int u, int v) {
        auto p = std::minmax(u, v);
        mp[p].emplace_back(t, 0);
    }
    void erase(int t, int u, int v) {
        auto p = std::minmax(u, v);
        mp[p].emplace_back(t, 1);
    }
    void build() {
        for (auto [p, v] : mp) {
            std::sort(v.begin(), v.end());
            int s = 0;
            int last = 0;
            for (auto q : v) {
                if (q.second == 0) {
                    if (s++ == 0) last = q.first;
                } else {
                    assert(s > 0);
                    if (--s == 0) apply(last, q.first, p);
                }
            }
            if (s) apply(last, _q, p);
        }
    }
    template <class F>
    void solve(F &&query, int k = 1) {
        int state = uf.snapshot();
        for (auto [u, v] : seg[k]) uf.unite(u, v);
        if (k < _size) {
            solve(query, 2 * k);
            solve(query, 2 * k + 1);
        } else if (k < _size + _q) {
            query(k - _size);
        }
        uf.rollback(state);
    }
    bool same(int u, int v) const { return uf.same(u, v); }
  private:
    int _q, _size, _log;
    std::vector<std::vector<std::pair<int, int>>> seg;
    std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> mp;
    undo_union_find uf;
    void apply(int l, int r, std::pair<int, int> p) {
        assert(0 <= l && l <= _q);
        assert(0 <= r && r <= _q);
        l += _size, r += _size;
        for (; l < r; l >>= 1, r >>= 1) {
            if (l & 1) seg[l++].emplace_back(p);
            if (r & 1) seg[--r].emplace_back(p);
        }
    }
};
int main(void) {
    int n, q;
    std::cin >> n >> q;
    offline_dynamic_connectivity dc(n, q);
    std::vector<std::tuple<int, int, int>> v(q);
    for (int i = 0; i < q; ++i) {
        auto &[a, b, c] = v[i];
        std::cin >> a >> b >> c;
        if (a == 1) {
            dc.add(i, b, c);
        } else if (a == 2) {
            dc.erase(i, b, c);
        }
    }
    dc.build();
    std::vector<int> ans(q);
    auto f = [&](int x) {
        auto [a, b, c] = v[x];
        if (a == 3)
            ans[x] = dc.same(b, c);
    };
    dc.solve(f);
    for (int i = 0; i < q; ++i) {
        auto [a, b, c] = v[i];
        if (a == 3) {
            std::cout << (ans[i] ? "YES\n" : "NO\n");
        }
    }
    return 0;
}
accuracy: 1
time_ms: 30
memory_kb: 14984
final_rank: 4

code_snippet: // AOJ #2235
// Graph Construction 2025.2.9

#include <bits/stdc++.h>
using namespace std;
 
struct DSU {
    vector<int> parent;
    vector<int> size;
    vector<tuple<int,int,int>> history;
    
    DSU(int n) {
        parent.resize(n);
        size.resize(n, 1);
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    
    int find(int a) {
        while(a != parent[a]) a = parent[a];
        return a;
    }
    
    bool unionSet(int a, int b) {
        a = find(a);
        b = find(b);
        if(a == b) return false;
        if(size[a] < size[b]) swap(a, b);
        history.push_back({a, b, size[a]});
        parent[b] = a;
        size[a] += size[b];
        return true;
    }
    
    void rollback(int checkpoint) {
        while((int)history.size() > checkpoint) {
            auto [a, b, oldSize] = history.back();
            history.pop_back();
            size[a] = oldSize;
            parent[b] = b;
        }
    }
};
 
struct Event {
    int type;
    int u, v;
};
 
struct Edge {
    int u, v;
};
 
struct EdgeInterval {
    int L, R;
    int u, v;
};
 
int n, k;
vector<Event> events;
 
vector<vector<Edge>> segTree;
 
void addEdge(int idx, int l, int r, int L, int R, Edge edge) {
    if(r <= L || R <= l) return;
    if(L <= l && r <= R) {
        segTree[idx].push_back(edge);
        return;
    }
    int mid = (l + r) / 2;
    addEdge(idx * 2 + 1, l, mid, L, R, edge);
    addEdge(idx * 2 + 2, mid, r, L, R, edge);
}
 
vector<string> answers;
void dfs(int idx, int l, int r, DSU &dsu) {
    int checkpoint = dsu.history.size();
    for(auto &edge : segTree[idx])
        dsu.unionSet(edge.u, edge.v);
    
    if(r - l == 1) {
        if(events[l].type == 3) {
            int u = events[l].u, v = events[l].v;
            answers.push_back(dsu.find(u) == dsu.find(v) ? "YES" : "NO");
        }
    } else {
        int mid = (l + r) / 2;
        dfs(idx * 2 + 1, l, mid, dsu);
        dfs(idx * 2 + 2, mid, r, dsu);
    }
    dsu.rollback(checkpoint);
}
 
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    cin >> n >> k;
    events.resize(k);
    for (int i = 0; i < k; i++){
        int t, u, v;
        cin >> t >> u >> v;
        events[i] = {t, u, v};
    }
 
    map<pair<int,int>, int> edgeStart;
    vector<EdgeInterval> intervals;
    for (int i = 0; i < k; i++){
        int t = events[i].type, u = events[i].u, v = events[i].v;
        if(t == 1) edgeStart[{u,v}] = i;
        else if(t == 2){
            auto it = edgeStart.find({u,v});
            if(it != edgeStart.end()){
                int startTime = it->second;
                intervals.push_back({startTime, i, u, v});
                edgeStart.erase(it);
            }
        }
    }
    for(auto &p : edgeStart){
        int startTime = p.second;
        int u = p.first.first, v = p.first.second;
        intervals.push_back({startTime, k, u, v});
    }
 
    segTree.resize(4 * k);
    for(auto &interv : intervals){
        Edge e = {interv.u, interv.v};
        addEdge(0, 0, k, interv.L, interv.R, e);
    }
    DSU dsu(n);
    dfs(0, 0, k, dsu);
    for(auto &ans : answers) cout << ans << endl;
    return 0;
}
accuracy: 1
time_ms: 20
memory_kb: 14044
final_rank: 3

code_snippet: #include <bits/stdc++.h>
#define ll long long
#define INF 1000000005
#define MOD 1000000007
#define EPS 1e-10
#define rep(i,n) for(int i=0;i<(int)(n);++i)
#define rrep(i,n) for(int i=(int)(n)-1;i>=0;--i)
#define srep(i,s,t) for(int i=(int)(s);i<(int)(t);++i)
#define each(a,b) for(auto& (a): (b))
#define all(v) (v).begin(),(v).end()
#define len(v) (int)(v).size()
#define zip(v) sort(all(v)),v.erase(unique(all(v)),v.end())
#define cmx(x,y) x=max(x,y)
#define cmn(x,y) x=min(x,y)
#define fi first
#define se second
#define pb push_back
#define show(x) cout<<#x<<" = "<<(x)<<endl
#define sar(a,n) cout<<#a<<":";rep(pachico,n)cout<<" "<<a[pachico];cout<<endl

using namespace std;

template<typename S,typename T>auto&operator<<(ostream&o,pair<S,T>p){return o<<"{"<<p.fi<<","<<p.se<<"}";}
template<typename T>auto&operator<<(ostream&o,set<T>s){for(auto&e:s)o<<e<<" ";return o;}
template<typename S,typename T,typename U>
auto&operator<<(ostream&o,priority_queue<S,T,U>q){while(!q.empty())o<<q.top()<<" ",q.pop();return o;}
template<typename K,typename T>auto&operator<<(ostream&o,map<K,T>m){for(auto&e:m)o<<e<<" ";return o;}
template<typename T>auto&operator<<(ostream&o,vector<T>v){for(auto&e:v)o<<e<<" ";return o;}
void ashow(){cout<<endl;}template<typename T,typename...A>void ashow(T t,A...a){cout<<t<<" ";ashow(a...);}

typedef pair<int,int> P;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef vector<P> vp;
typedef vector<double> vd;
typedef vector<string> vs;

const int MAX_N = 100005;

// 多重辺は無いと仮定する
class BSTNode {
public:
    const int from, to;
    int sz;
    bool subtree_edge, subofftree_edge, exact_level;
    BSTNode *left, *right, *par;
    unordered_set<int> adjacent;
    BSTNode(const int _ver) noexcept :
        from(_ver), to(_ver), sz(0), subtree_edge(false), subofftree_edge(false),
            exact_level(false), left(nullptr), right(nullptr), par(nullptr){}
    BSTNode(const int _from, const int _to, const bool _flag) noexcept :
        from(_from), to(_to), sz(0), subtree_edge(false), subofftree_edge(false),
            exact_level((from < to) && _flag), left(nullptr), right(nullptr), par(nullptr){}
    inline bool IsRoot() const noexcept { return !par; }
    inline bool IsVertex() const noexcept { return (from == to); }
    inline void eval() noexcept {
        if(IsVertex()) sz = 1, subtree_edge = false, subofftree_edge = !adjacent.empty();
        else sz = 0, subtree_edge = exact_level, subofftree_edge = false;
        if(left){
            sz += left->sz, subtree_edge |= left->subtree_edge, subofftree_edge |= left->subofftree_edge;
        }
        if(right){
            sz += right->sz, subtree_edge |= right->subtree_edge, subofftree_edge |= right->subofftree_edge;
        }
    }
    inline bool subtree_edge_eval(){
        subtree_edge = exact_level;
        if(left) subtree_edge |= left->subtree_edge;
        if(right) subtree_edge |= right->subtree_edge;
        return subtree_edge;
    }
    inline bool subofftree_edge_eval(){
        subofftree_edge = !adjacent.empty();
        if(left) subofftree_edge |= left->subofftree_edge;
        if(right) subofftree_edge |= right->subofftree_edge;
        return subofftree_edge;
    }
    void rotate(const bool right_) noexcept {
        BSTNode *p = par, *g = p->par;
        if(right_){
            if((p->left = right)) right->par = p;
            right = p, p->par = this;
        }else{
            if((p->right = left)) left->par = p;
            left = p, p->par = this;
        }
        p->eval(), eval();
        if(!(par = g)) return;
        if(g->left == p) g->left = this;
        if(g->right == p) g->right = this;
        g->eval();
    }
};

BSTNode *splay(BSTNode *u) noexcept {
    if(!u) return nullptr;
    while(!(u->IsRoot())){
        BSTNode *p = u->par, *gp = p->par;
        if(p->IsRoot()){ // zig
            u->rotate((u == p->left));
        }else{
            bool flag = (u == p->left);
            if((u == p->left) == (p == gp->left)){ // zig-zig
                p->rotate(flag), u->rotate(flag);
            }else{ // zig-zag
                u->rotate(flag), u->rotate(!flag);
            }
        }
    }
    return u;
}

BSTNode *join(BSTNode *root1, BSTNode *root2) noexcept {
    if(!root1 || !root2) return root1 ? root1 : root2;
    BSTNode *cur = nullptr, *nx = root1;
    do{ cur = nx, nx = cur->right; }while(nx);
    BSTNode *ver = splay(cur);
    ver->right = root2, ver->eval(), root2->par = ver;
    return ver;
}

class EulerTourTree {
public:
    struct pair_hash {
        template <class T1, class T2>
        size_t operator() (const pair<T1, T2>& p) const {
            size_t lhs = hash<T1>()(p.first), rhs = hash<T2>()(p.second);
            return lhs^(rhs+0x9e3779b9+(lhs<<6)+(lhs>>2));
        }
    };
    BSTNode** vertex_set;
    unordered_map<pair<int, int>, pair<BSTNode*, BSTNode*>, pair_hash> edge_set;
private:
    BSTNode *reroot(BSTNode *ver) noexcept {
        BSTNode *res = splay(ver)->left;
        if(!res) return ver;
        ver->left = nullptr, ver->eval();
        while(ver->right) ver = ver->right;
        splay(ver), ver->right = res, ver->eval(), res->par = ver;
        return ver;
    }
    void link(BSTNode *ver1, BSTNode *ver2, const bool flag) noexcept {
        BSTNode *e1 = new BSTNode(ver1->from, ver2->from, flag);
        BSTNode *e2 = new BSTNode(ver2->from, ver1->from, flag);
        edge_set[{ver1->from, ver2->from}] = {e1, e2};
        join(join(reroot(ver1), e1), join(reroot(ver2), e2));
    }
    void cut(BSTNode *edge1, BSTNode *edge2) noexcept {
        splay(edge1), splay(edge2);
        BSTNode *p = edge1->par;
        bool _right = (edge1 == edge2->right);
        if(p != edge2){
            _right = (p == edge2->right);
            p->par = nullptr, edge1->rotate((edge1 == p->left));
        }
        if(edge1->left) edge1->left->par = nullptr;
        if(edge1->right) edge1->right->par = nullptr;
        if(_right){
            if(edge2->left) edge2->left->par = nullptr;
            join(edge2->left, edge1->right);
        }else{
            if(edge2->right) edge2->right->par = nullptr;
            join(edge1->left, edge2->right);
        }
        delete edge1; delete edge2;
    }
    bool connected(BSTNode *ver1, BSTNode *ver2) noexcept {
        splay(ver1), splay(ver2);
        return ver1->par;
    }
    int component_size(BSTNode *ver) noexcept { return splay(ver)->sz; }
public:
    int V;
    // ~EulerTourTree(){
    //     for(auto it : edge_set){
    //         delete (it.second).first;
    //         delete (it.second).second;
    //     }
    //     for(int i = 0; i < V; ++i) delete vertex_set[i];
    //     delete[] vertex_set;
    // }
    EulerTourTree(const int node_size) noexcept {
        V = node_size, vertex_set = new BSTNode*[V];
        for(int i = 0; i < V; i++) vertex_set[i] = new BSTNode(i);
    }
    void reroot(const int node_id) noexcept { reroot(vertex_set[node_id]); }
    void link(int node1_id, int node2_id, bool flag=true) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        link(vertex_set[node1_id], vertex_set[node2_id], flag);
    }
    void cut(int node1_id, int node2_id){
        if(node1_id > node2_id) swap(node1_id, node2_id);
        auto it = edge_set.find({node1_id, node2_id});
        assert(it != edge_set.end());
        BSTNode *edge1 = (it->second).first, *edge2 = (it->second).second;
        edge_set.erase(it);
        cut(edge1, edge2);
    }
    bool connected(const int node1_id, const int node2_id) noexcept {
        if(node1_id == node2_id) return true;
        return connected(vertex_set[node1_id], vertex_set[node2_id]);
    }
    int component_size(int node_id) noexcept { return component_size(vertex_set[node_id]); }
    void check_dfs(const BSTNode* cur) const noexcept {
        if(cur->left) check_dfs(cur->left);
        cout << "{" << (cur->from) << "," << (cur->to) << "} ";
        if(cur->right) check_dfs(cur->right);
    }
};

// 頂点ノードにそこから出る level i の off tree edge の集合(両方)を乗せる
// 頂点ノードには map でアクセス可能
// link のとき tree edge ならそのまま link
// off tree edge なら level 0 の頂点ノードにそいつを追加
// cut のとき level i の tree edge を level i+1 に押し上げるのだが
// 各 layer の Euler Tree は level i 以上の edge からなるのでその辺が exact に level i の辺であるかをもつ必要がある.
// 基本的に部分木の情報を書き換えるときは(splay してから変更する)根にしてから反映しないとマズい.
// 保持している offtree edge って offtree edge の名の通り常にその両端点は同じ連結成分に属する.

class DynamicConnectivity {
private:
    BSTNode *level_up_dfs(BSTNode *cur, const int layer) noexcept {
        if(cur->exact_level){
            splay(cur)->exact_level = false, cur->subtree_edge_eval();
            detect_layer[{cur->from, cur->to}]++, et[layer+1].link(cur->from, cur->to);
            return cur;
        }
        if(cur->left && cur->left->subtree_edge) return level_up_dfs(cur->left, layer);
        if(cur->right && cur->right->subtree_edge) return level_up_dfs(cur->right, layer);
        return nullptr;
    }
    // another は (u, v) を削除しているけどそのうち larger connected component に属する方
    BSTNode *search_edge_dfs
        (BSTNode *cur, const int layer, const int another, bool& flag, pair<int, int>& rep_edge) noexcept {
        // off_tree edge がある
        if(!cur->adjacent.empty()){
            bool state = et[layer+1].vertex_set[cur->from]->adjacent.empty();
            for(auto it = cur->adjacent.begin(); it != cur->adjacent.end();){
                pair<int, int> e = {min(cur->from, *it), max(cur->from, *it)};
                BSTNode *correspond = et[layer].vertex_set[*it];
                if(et[layer].connected(another, *it)){
                    // another と今見ている先が connected つまり, restore する edge なら
                    flag = true, rep_edge = e;
                    cur->adjacent.erase(it), correspond->adjacent.erase(cur->from);
                    // correspond の方で subofftree フラグが壊れるのを防ぐ.
                    if(!correspond->subofftree_edge_eval()){
                        splay(correspond)->subofftree_edge_eval();
                    }
                    break;
                }else{
                    // 今見ている connected component をつなぐ edge なら suboff_tree edge の情報変える
                    if(!et[layer+1].vertex_set[*it]->subofftree_edge_eval()){
                        splay(et[layer+1].vertex_set[*it])->subofftree_edge = true;
                    }
                    et[layer+1].vertex_set[cur->from]->adjacent.insert(*it);
                    et[layer+1].vertex_set[*it]->adjacent.insert(cur->from);
                    detect_layer[e]++, it = cur->adjacent.erase(it);
                    correspond->adjacent.erase(cur->from);
                    if(!correspond->subofftree_edge_eval()){
                        splay(correspond)->subofftree_edge_eval();
                    }
                }
            }
            // 元々 ajacent が empty で off_tree edge が無かったのに今 cur->from から出る adjacent がある場合
            // でかつ left, right の off_tree edge が false なら subofftree_edge を true にする(これ何してんの？)
            // left もしくは right の off_tree edge が true なら根まで offtree_edge ありますよの伝播がちゃんと行っているので
            // ajacent list が新しく non empty になったけど別に更新する必要はないな
            if(state && !et[layer+1].vertex_set[cur->from]->subtree_edge_eval()){
                splay(et[layer+1].vertex_set[cur->from])->subofftree_edge = true;
            }
            splay(cur)->subofftree_edge_eval();
            return cur;
        }
        if(cur->left && cur->left->subofftree_edge){
            return search_edge_dfs(cur->left, layer, another, flag, rep_edge);
        }
        if(cur->right && cur->right->subofftree_edge){
            return search_edge_dfs(cur->right, layer, another, flag, rep_edge);
        }
        return nullptr;
    }
    bool replace(const int from, const int to, const int layer) noexcept {
        if(layer < 0) return true;
        int u, v;
        if(et[layer].component_size(from) <= et[layer].component_size(to)) u = from, v = to;
        else u = to, v = from;
        BSTNode *ver = splay(et[layer].vertex_set[u]);
        // smaller connected component に tree edge があるならそれの level を 1 個上げる
        while(ver->subtree_edge) ver = level_up_dfs(ver, layer);
        pair<int, int> rep_edge = {-1, -1};
        bool flag = false;
        // smaller connected component に offtree edge があり,
        // その辺が連結成分を再びつなげるようなものでないなら 1 増やす
        // flag 見つけたらすぐ終了するという実装にしてるけどそれでもならしが回るので問題ない
        // 1 回探索する度に restore edge が見つかって探索が終了する or 何かの辺の level が 1 上がる が言えるので
        while(ver->subofftree_edge){
            ver = search_edge_dfs(ver, layer, v, flag, rep_edge);
            if(flag) break;
        }
        if(flag){
            // restore edge を見つけたなら
            et[layer].link(rep_edge.first, rep_edge.second);
            for(int i = 0; i < layer; i++) et[i].link(rep_edge.first, rep_edge.second, false);
            return false;
        }else return replace(from, to, layer-1);
    }
public:
    const int V;
    int depth;
    vector<EulerTourTree> et;
    unordered_map<pair<int, int>, int, EulerTourTree::pair_hash> detect_layer;
    DynamicConnectivity(const int node_size) noexcept : V(node_size), depth(1){
        et.emplace_back(V);
    }
    // ~DynamicConnectivity(){
    //     delete[] et;
    // }
    bool link(int node1_id, int node2_id) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        detect_layer[{node1_id, node2_id}] = 0;
        if(et[0].connected(node1_id, node2_id)){
            BSTNode *ver1 = et[0].vertex_set[node1_id], *ver2 = et[0].vertex_set[node2_id];
            splay(ver1)->subofftree_edge = true, ver1->adjacent.insert(node2_id);
            splay(ver2)->subofftree_edge = true, ver2->adjacent.insert(node1_id);
            return false;
        }else{
            et[0].link(node1_id, node2_id);
            return true;
        }
    }
    bool cut(int node1_id, int node2_id) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        auto it = detect_layer.find({node1_id, node2_id});
        assert(it != detect_layer.end());
        int layer = it->second;
        detect_layer.erase(it);
        auto& st = et[layer].vertex_set[node1_id]->adjacent;
        if(st.find(node2_id) == st.end()){
            // tree edge の場合
            for(int i = 0; i <= layer; i++) et[i].cut(node1_id, node2_id);
            if(layer + 1 == depth) ++depth, et.emplace_back(V);
            return replace(node1_id, node2_id, layer);
        }else{
            // off_tree edge の場合
            et[layer].vertex_set[node1_id]->adjacent.erase(node2_id);
            // 自分より下に node1_id より下に off tree_edge があるか
            // (もしなかったらこれより上のノードについての off tree edge があるかどうかの情報を書き換える必要がある)
            if(!et[layer].vertex_set[node1_id]->subofftree_edge_eval()){
                splay(et[layer].vertex_set[node1_id])->subofftree_edge_eval();
            }
            et[layer].vertex_set[node2_id]->adjacent.erase(node1_id);
            if(!et[layer].vertex_set[node2_id]->subofftree_edge_eval()){
                splay(et[layer].vertex_set[node2_id])->subofftree_edge_eval();
            }
            return false;
        }
    }
    bool connected(const int node1_id, const int node2_id) noexcept {
        return et[0].connected(node1_id, node2_id);
    }
};

#define getchar getchar_unlocked
#define putchar putchar_unlocked

inline int in() {
    int n = 0; short c;
    while ((c = getchar()) >= '0') n = n * 10 + c - '0';
    return n;
}

inline void out(int n) {
    short res[10], i = 0;
    do { res[i++] = n % 10, n /= 10; } while (n);
    while (i) putchar(res[--i] + '0');
    putchar('\n');
}

int main()
{
    int n = in(), m = in();
    DynamicConnectivity dc(n);
    rep(i,m){
        int a,b,c;
        a = in(), b = in(), c = in();
        if(a == 1){
            dc.link(b, c);
        }else if(a == 2){
            dc.cut(b, c);
        }else{
            if(dc.connected(b,c)){
                cout << "YES\n";
            }else{
                cout << "NO\n";
            }
        }
    }
    return 0;
}
accuracy: 1
time_ms: 230
memory_kb: 76224
final_rank: 12

code_snippet: #include <bits/stdc++.h>
#define ll long long
#define INF 1000000005
#define MOD 1000000007
#define EPS 1e-10
#define rep(i,n) for(int i=0;i<(int)(n);++i)
#define rrep(i,n) for(int i=(int)(n)-1;i>=0;--i)
#define srep(i,s,t) for(int i=(int)(s);i<(int)(t);++i)
#define each(a,b) for(auto& (a): (b))
#define all(v) (v).begin(),(v).end()
#define len(v) (int)(v).size()
#define zip(v) sort(all(v)),v.erase(unique(all(v)),v.end())
#define cmx(x,y) x=max(x,y)
#define cmn(x,y) x=min(x,y)
#define fi first
#define se second
#define pb push_back
#define show(x) cout<<#x<<" = "<<(x)<<endl
#define sar(a,n) cout<<#a<<":";rep(pachico,n)cout<<" "<<a[pachico];cout<<endl

using namespace std;

template<typename S,typename T>auto&operator<<(ostream&o,pair<S,T>p){return o<<"{"<<p.fi<<","<<p.se<<"}";}
template<typename T>auto&operator<<(ostream&o,set<T>s){for(auto&e:s)o<<e<<" ";return o;}
template<typename S,typename T,typename U>
auto&operator<<(ostream&o,priority_queue<S,T,U>q){while(!q.empty())o<<q.top()<<" ",q.pop();return o;}
template<typename K,typename T>auto&operator<<(ostream&o,map<K,T>m){for(auto&e:m)o<<e<<" ";return o;}
template<typename T>auto&operator<<(ostream&o,vector<T>v){for(auto&e:v)o<<e<<" ";return o;}
void ashow(){cout<<endl;}template<typename T,typename...A>void ashow(T t,A...a){cout<<t<<" ";ashow(a...);}

typedef pair<int,int> P;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef vector<P> vp;
typedef vector<double> vd;
typedef vector<string> vs;

const int MAX_N = 100005;

// 多重辺は無いと仮定する
class BSTNode {
public:
    const int from, to;
    int sz;
    bool subtree_edge, subofftree_edge, exact_level;
    BSTNode *left, *right, *par;
    unordered_set<int> adjacent;
    BSTNode(const int _ver) noexcept :
        from(_ver), to(_ver), sz(0), subtree_edge(false), subofftree_edge(false),
            exact_level(false), left(nullptr), right(nullptr), par(nullptr){}
    BSTNode(const int _from, const int _to, const bool _flag) noexcept :
        from(_from), to(_to), sz(0), subtree_edge(false), subofftree_edge(false),
            exact_level((from < to) && _flag), left(nullptr), right(nullptr), par(nullptr){}
    inline bool IsRoot() const noexcept { return !par; }
    inline bool IsVertex() const noexcept { return (from == to); }
    inline void eval() noexcept {
        if(IsVertex()) sz = 1, subtree_edge = false, subofftree_edge = !adjacent.empty();
        else sz = 0, subtree_edge = exact_level, subofftree_edge = false;
        if(left){
            sz += left->sz, subtree_edge |= left->subtree_edge, subofftree_edge |= left->subofftree_edge;
        }
        if(right){
            sz += right->sz, subtree_edge |= right->subtree_edge, subofftree_edge |= right->subofftree_edge;
        }
    }
    inline void subtree_edge_eval(){
        subtree_edge = exact_level;
        if(left) subtree_edge |= left->subtree_edge;
        if(right) subtree_edge |= right->subtree_edge;
    }
    inline void subofftree_edge_eval(){
        subofftree_edge = !adjacent.empty();
        if(left) subofftree_edge |= left->subofftree_edge;
        if(right) subofftree_edge |= right->subofftree_edge;
    }
    inline bool subofftree_check(){
        return !adjacent.empty() ||
                (left ? left->subofftree_edge : false) || (right ? right->subofftree_edge : false);
    }
    inline bool offtree_check(){
        return adjacent.empty() ||
                (left ? left->subofftree_edge : false) || (right ? right->subofftree_edge : false);
    }
    void rotate(const bool right_) noexcept {
        BSTNode *p = par, *g = p->par;
        if(right_){
            if((p->left = right)) right->par = p;
            right = p, p->par = this;
        }else{
            if((p->right = left)) left->par = p;
            left = p, p->par = this;
        }
        p->eval(), eval();
        if(!(par = g)) return;
        if(g->left == p) g->left = this;
        if(g->right == p) g->right = this;
        g->eval();
    }
};

BSTNode *splay(BSTNode *u) noexcept {
    if(!u) return nullptr;
    while(!(u->IsRoot())){
        BSTNode *p = u->par, *gp = p->par;
        if(p->IsRoot()){ // zig
            u->rotate((u == p->left));
        }else{
            bool flag = (u == p->left);
            if((u == p->left) == (p == gp->left)){ // zig-zig
                p->rotate(flag), u->rotate(flag);
            }else{ // zig-zag
                u->rotate(flag), u->rotate(!flag);
            }
        }
    }
    return u;
}

BSTNode *join(BSTNode *root1, BSTNode *root2) noexcept {
    if(!root1 || !root2) return root1 ? root1 : root2;
    BSTNode *cur = nullptr, *nx = root1;
    do{ cur = nx, nx = cur->right; }while(nx);
    BSTNode *ver = splay(cur);
    ver->right = root2, ver->eval(), root2->par = ver;
    return ver;
}

class EulerTourTree {
public:
    struct pair_hash {
        template <class T1, class T2>
        size_t operator() (const pair<T1, T2>& p) const {
            size_t lhs = hash<T1>()(p.first), rhs = hash<T2>()(p.second);
            return lhs^(rhs+0x9e3779b9+(lhs<<6)+(lhs>>2));
        }
    };
    BSTNode** vertex_set;
    unordered_map<pair<int, int>, pair<BSTNode*, BSTNode*>, pair_hash> edge_set;
private:
    BSTNode *reroot(BSTNode *ver) noexcept {
        BSTNode *res = splay(ver)->left;
        if(!res) return ver;
        ver->left = nullptr, ver->eval();
        while(ver->right) ver = ver->right;
        splay(ver), ver->right = res, ver->eval(), res->par = ver;
        return ver;
    }
    void link(BSTNode *ver1, BSTNode *ver2, const bool flag) noexcept {
        BSTNode *e1 = new BSTNode(ver1->from, ver2->from, flag);
        BSTNode *e2 = new BSTNode(ver2->from, ver1->from, flag);
        edge_set[{ver1->from, ver2->from}] = {e1, e2};
        join(join(reroot(ver1), e1), join(reroot(ver2), e2));
    }
    void cut(BSTNode *edge1, BSTNode *edge2) noexcept {
        splay(edge1), splay(edge2);
        BSTNode *p = edge1->par;
        bool _right = (edge1 == edge2->right);
        if(p != edge2){
            _right = (p == edge2->right);
            p->par = nullptr, edge1->rotate((edge1 == p->left));
        }
        if(edge1->left) edge1->left->par = nullptr;
        if(edge1->right) edge1->right->par = nullptr;
        if(_right){
            if(edge2->left) edge2->left->par = nullptr;
            join(edge2->left, edge1->right);
        }else{
            if(edge2->right) edge2->right->par = nullptr;
            join(edge1->left, edge2->right);
        }
        delete edge1; delete edge2;
    }
    bool connected(BSTNode *ver1, BSTNode *ver2) noexcept {
        splay(ver1), splay(ver2);
        return ver1->par;
    }
    int component_size(BSTNode *ver) noexcept { return splay(ver)->sz; }
public:
    int V;
    // ~EulerTourTree(){
    //     for(auto it : edge_set){
    //         delete (it.second).first;
    //         delete (it.second).second;
    //     }
    //     for(int i = 0; i < V; ++i) delete vertex_set[i];
    //     delete[] vertex_set;
    // }
    EulerTourTree(const int node_size) noexcept {
        V = node_size, vertex_set = new BSTNode*[V];
        for(int i = 0; i < V; i++) vertex_set[i] = new BSTNode(i);
    }
    void reroot(const int node_id) noexcept { reroot(vertex_set[node_id]); }
    void link(int node1_id, int node2_id, bool flag=true) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        link(vertex_set[node1_id], vertex_set[node2_id], flag);
    }
    void cut(int node1_id, int node2_id){
        if(node1_id > node2_id) swap(node1_id, node2_id);
        auto it = edge_set.find({node1_id, node2_id});
        assert(it != edge_set.end());
        BSTNode *edge1 = (it->second).first, *edge2 = (it->second).second;
        edge_set.erase(it);
        cut(edge1, edge2);
    }
    bool connected(const int node1_id, const int node2_id) noexcept {
        if(node1_id == node2_id) return true;
        return connected(vertex_set[node1_id], vertex_set[node2_id]);
    }
    int component_size(int node_id) noexcept { return component_size(vertex_set[node_id]); }
    void check_dfs(const BSTNode* cur) const noexcept {
        if(cur->left) check_dfs(cur->left);
        cout << "{" << (cur->from) << "," << (cur->to) << "} ";
        if(cur->right) check_dfs(cur->right);
    }
};

// 頂点ノードにそこから出る level i の off tree edge の集合(両方)を乗せる
// 頂点ノードには map でアクセス可能
// link のとき tree edge ならそのまま link
// off tree edge なら level 0 の頂点ノードにそいつを追加
// cut のとき level i の tree edge を level i+1 に押し上げるのだが
// 各 layer の Euler Tree は level i 以上の edge からなるのでその辺が exact に level i の辺であるかをもつ必要がある.
// 基本的に部分木の情報を書き換えるときは(splay してから変更する)根にしてから反映しないとマズい.
// 保持している offtree edge って offtree edge の名の通り常にその両端点は同じ連結成分に属する.

class DynamicConnectivity {
private:
    BSTNode *level_up_dfs(BSTNode *cur, const int layer) noexcept {
        if(cur->exact_level){
            splay(cur)->exact_level = false, cur->subtree_edge_eval();
            detect_layer[{cur->from, cur->to}]++, et[layer+1].link(cur->from, cur->to);
            return cur;
        }
        if(cur->left && cur->left->subtree_edge) return level_up_dfs(cur->left, layer);
        if(cur->right && cur->right->subtree_edge) return level_up_dfs(cur->right, layer);
        return nullptr;
    }
    // another は (u, v) を削除しているけどそのうち larger connected component に属する方
    BSTNode *search_edge_dfs
        (BSTNode *cur, const int layer, const int another, bool& flag, pair<int, int>& rep_edge) noexcept {
        // off_tree edge がある
        assert(cur->left == cur->right);
        if(!cur->adjacent.empty()){
            bool state = et[layer+1].vertex_set[cur->from]->adjacent.empty();
            for(auto it = cur->adjacent.begin(); it != cur->adjacent.end();){
                pair<int, int> e = {min(cur->from, *it), max(cur->from, *it)};
                BSTNode *correspond = et[layer].vertex_set[*it];
                if(et[layer].connected(another, *it)){
                    // another と今見ている先が connected つまり, restore する edge なら
                    flag = true, rep_edge = e;
                    cur->adjacent.erase(it), correspond->adjacent.erase(cur->from);
                    // correspond の方で subofftree フラグが壊れるのを防ぐ.
                    if(!correspond->subofftree_check()){
                        splay(correspond)->subofftree_edge_eval();
                    }
                    break;
                }else{
                    // 今見ている connected component をつなぐ edge なら suboff_tree edge の情報変える
                    if(!et[layer+1].vertex_set[*it]->subofftree_check()){
                        splay(et[layer+1].vertex_set[*it])->subofftree_edge = true;
                    }
                    et[layer+1].vertex_set[cur->from]->adjacent.insert(*it);
                    et[layer+1].vertex_set[*it]->adjacent.insert(cur->from);
                    detect_layer[e]++, it = cur->adjacent.erase(it);
                    correspond->adjacent.erase(cur->from);
                    if(!correspond->subofftree_check()){
                        splay(correspond)->subofftree_edge_eval();
                    }
                }
            }
            // 元々 ajacent が empty で off_tree edge が無かったのに今 cur->from から出る adjacent がある場合
            // でかつ left, right の off_tree edge が false なら subofftree_edge を true にする(これ何してんの？)
            // left もしくは right の off_tree edge が true なら根まで offtree_edge ありますよの伝播がちゃんと行っているので
            // ajacent list が新しく non empty になったけど別に更新する必要はないな
            if(state && !et[layer+1].vertex_set[cur->from]->offtree_check()){
                splay(et[layer+1].vertex_set[cur->from])->subofftree_edge = true;
            }
            splay(cur)->subofftree_edge_eval();
            return cur;
        }
        if(cur->left && cur->left->subofftree_edge){
            return search_edge_dfs(cur->left, layer, another, flag, rep_edge);
        }
        if(cur->right && cur->right->subofftree_edge){
            return search_edge_dfs(cur->right, layer, another, flag, rep_edge);
        }
        return nullptr;
    }
    bool replace(const int from, const int to, const int layer) noexcept {
        if(layer < 0) return true;
        int u, v;
        if(et[layer].component_size(from) <= et[layer].component_size(to)) u = from, v = to;
        else u = to, v = from;
        BSTNode *ver = splay(et[layer].vertex_set[u]);
        // smaller connected component に tree edge があるならそれの level を 1 個上げる
        while(ver->subtree_edge) ver = level_up_dfs(ver, layer);
        pair<int, int> rep_edge = {-1, -1};
        bool flag = false;
        // smaller connected component に offtree edge があり,
        // その辺が連結成分を再びつなげるようなものでないなら 1 増やす
        // flag 見つけたらすぐ終了するという実装にしてるけどそれでもならしが回るので問題ない
        // 1 回探索する度に restore edge が見つかって探索が終了する or 何かの辺の level が 1 上がる が言えるので
        while(ver->subofftree_edge){
            ver = search_edge_dfs(ver, layer, v, flag, rep_edge);
            if(flag) break;
        }
        if(flag){
            // restore edge を見つけたなら
            et[layer].link(rep_edge.first, rep_edge.second);
            for(int i = 0; i < layer; i++) et[i].link(rep_edge.first, rep_edge.second, false);
            return false;
        }else return replace(from, to, layer-1);
    }
public:
    const int V;
    int depth;
    vector<EulerTourTree> et;
    unordered_map<pair<int, int>, int, EulerTourTree::pair_hash> detect_layer;
    DynamicConnectivity(const int node_size) noexcept : V(node_size), depth(1){
        et.emplace_back(V);
    }
    // ~DynamicConnectivity(){
    //     delete[] et;
    // }
    bool link(int node1_id, int node2_id) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        detect_layer[{node1_id, node2_id}] = 0;
        if(et[0].connected(node1_id, node2_id)){
            BSTNode *ver1 = et[0].vertex_set[node1_id], *ver2 = et[0].vertex_set[node2_id];
            splay(ver1)->subofftree_edge = true, ver1->adjacent.insert(node2_id);
            splay(ver2)->subofftree_edge = true, ver2->adjacent.insert(node1_id);
            return false;
        }else{
            et[0].link(node1_id, node2_id);
            return true;
        }
    }
    bool cut(int node1_id, int node2_id) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        auto it = detect_layer.find({node1_id, node2_id});
        assert(it != detect_layer.end());
        int layer = it->second;
        detect_layer.erase(it);
        auto& st = et[layer].vertex_set[node1_id]->adjacent;
        if(st.find(node2_id) == st.end()){
            // tree edge の場合
            for(int i = 0; i <= layer; i++) et[i].cut(node1_id, node2_id);
            if(layer + 1 == depth) ++depth, et.emplace_back(V);
            return replace(node1_id, node2_id, layer);
        }else{
            // off_tree edge の場合
            et[layer].vertex_set[node1_id]->adjacent.erase(node2_id);
            // 自分より下に node1_id より下に off tree_edge があるか
            // (もしなかったらこれより上のノードについての off tree edge があるかどうかの情報を書き換える必要がある)
            if(!et[layer].vertex_set[node1_id]->subofftree_check()){
                splay(et[layer].vertex_set[node1_id])->subofftree_edge_eval();
            }
            et[layer].vertex_set[node2_id]->adjacent.erase(node1_id);
            if(!et[layer].vertex_set[node2_id]->subofftree_check()){
                splay(et[layer].vertex_set[node2_id])->subofftree_edge_eval();
            }
            return false;
        }
    }
    bool connected(const int node1_id, const int node2_id) noexcept {
        return et[0].connected(node1_id, node2_id);
    }
};

#define getchar getchar_unlocked
#define putchar putchar_unlocked

inline int in() {
    int n = 0; short c;
    while ((c = getchar()) >= '0') n = n * 10 + c - '0';
    return n;
}

inline void out(int n) {
    short res[10], i = 0;
    do { res[i++] = n % 10, n /= 10; } while (n);
    while (i) putchar(res[--i] + '0');
    putchar('\n');
}

int main()
{
    int n = in(), m = in();
    DynamicConnectivity dc(n);
    rep(i,m){
        int a,b,c;
        a = in(), b = in(), c = in();
        if(a == 1){
            dc.link(b, c);
        }else if(a == 2){
            dc.cut(b, c);
        }else{
            if(dc.connected(b,c)){
                cout << "YES\n";
            }else{
                cout << "NO\n";
            }
        }
    }
    return 0;
}
accuracy: 0.3684210526315789
time_ms: 10
memory_kb: 11460
final_rank: 19

code_snippet: #include <bits/stdc++.h>
#define ll long long
#define INF 1000000005
#define MOD 1000000007
#define EPS 1e-10
#define rep(i,n) for(int i=0;i<(int)(n);++i)
#define rrep(i,n) for(int i=(int)(n)-1;i>=0;--i)
#define srep(i,s,t) for(int i=(int)(s);i<(int)(t);++i)
#define each(a,b) for(auto& (a): (b))
#define all(v) (v).begin(),(v).end()
#define len(v) (int)(v).size()
#define zip(v) sort(all(v)),v.erase(unique(all(v)),v.end())
#define cmx(x,y) x=max(x,y)
#define cmn(x,y) x=min(x,y)
#define fi first
#define se second
#define pb push_back
#define show(x) cout<<#x<<" = "<<(x)<<endl
#define sar(a,n) cout<<#a<<":";rep(pachico,n)cout<<" "<<a[pachico];cout<<endl

using namespace std;

template<typename S,typename T>auto&operator<<(ostream&o,pair<S,T>p){return o<<"{"<<p.fi<<","<<p.se<<"}";}
template<typename T>auto&operator<<(ostream&o,set<T>s){for(auto&e:s)o<<e<<" ";return o;}
template<typename S,typename T,typename U>
auto&operator<<(ostream&o,priority_queue<S,T,U>q){while(!q.empty())o<<q.top()<<" ",q.pop();return o;}
template<typename K,typename T>auto&operator<<(ostream&o,map<K,T>m){for(auto&e:m)o<<e<<" ";return o;}
template<typename T>auto&operator<<(ostream&o,vector<T>v){for(auto&e:v)o<<e<<" ";return o;}
void ashow(){cout<<endl;}template<typename T,typename...A>void ashow(T t,A...a){cout<<t<<" ";ashow(a...);}

typedef pair<int,int> P;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef vector<P> vp;
typedef vector<double> vd;
typedef vector<string> vs;

const int MAX_N = 100005;

// 多重辺は無いと仮定する
class BSTNode {
public:
    const int from, to;
    int sz;
    bool subtree_edge, subofftree_edge, exact_level;
    BSTNode *left, *right, *par;
    unordered_set<int> adjacent;
    BSTNode(const int _ver) noexcept :
        from(_ver), to(_ver), sz(0), subtree_edge(false), subofftree_edge(false),
            exact_level(false), left(nullptr), right(nullptr), par(nullptr){}
    BSTNode(const int _from, const int _to, const bool _flag) noexcept :
        from(_from), to(_to), sz(0), subtree_edge(false), subofftree_edge(false),
            exact_level((from < to) && _flag), left(nullptr), right(nullptr), par(nullptr){}
    inline bool IsRoot() const noexcept { return !par; }
    inline bool IsVertex() const noexcept { return (from == to); }
    inline void eval() noexcept {
        if(IsVertex()) sz = 1, subtree_edge = false, subofftree_edge = !adjacent.empty();
        else sz = 0, subtree_edge = exact_level, subofftree_edge = false;
        if(left){
            sz += left->sz, subtree_edge |= left->subtree_edge, subofftree_edge |= left->subofftree_edge;
        }
        if(right){
            sz += right->sz, subtree_edge |= right->subtree_edge, subofftree_edge |= right->subofftree_edge;
        }
    }
    inline void subtree_edge_eval(){
        subtree_edge = exact_level;
        if(left) subtree_edge |= left->subtree_edge;
        if(right) subtree_edge |= right->subtree_edge;
    }
    inline void subofftree_edge_eval(){
        subofftree_edge = !adjacent.empty();
        if(left) subofftree_edge |= left->subofftree_edge;
        if(right) subofftree_edge |= right->subofftree_edge;
    }
    inline bool subofftree_check(){
        return !adjacent.empty() ||
                (left ? left->subofftree_edge : false) || (right ? right->subofftree_edge : false);
    }
    inline bool offtree_check(){
        return adjacent.empty() ||
                (left ? left->subofftree_edge : false) || (right ? right->subofftree_edge : false);
    }
    void rotate(const bool right_) noexcept {
        BSTNode *p = par, *g = p->par;
        if(right_){
            if((p->left = right)) right->par = p;
            right = p, p->par = this;
        }else{
            if((p->right = left)) left->par = p;
            left = p, p->par = this;
        }
        p->eval(), eval();
        if(!(par = g)) return;
        if(g->left == p) g->left = this;
        if(g->right == p) g->right = this;
        g->eval();
    }
};

BSTNode *splay(BSTNode *u) noexcept {
    if(!u) return nullptr;
    while(!(u->IsRoot())){
        BSTNode *p = u->par, *gp = p->par;
        if(p->IsRoot()){ // zig
            u->rotate((u == p->left));
        }else{
            bool flag = (u == p->left);
            if((u == p->left) == (p == gp->left)){ // zig-zig
                p->rotate(flag), u->rotate(flag);
            }else{ // zig-zag
                u->rotate(flag), u->rotate(!flag);
            }
        }
    }
    return u;
}

BSTNode *join(BSTNode *root1, BSTNode *root2) noexcept {
    if(!root1 || !root2) return root1 ? root1 : root2;
    BSTNode *cur = nullptr, *nx = root1;
    do{ cur = nx, nx = cur->right; }while(nx);
    BSTNode *ver = splay(cur);
    ver->right = root2, ver->eval(), root2->par = ver;
    return ver;
}

class EulerTourTree {
public:
    struct pair_hash {
        template <class T1, class T2>
        size_t operator() (const pair<T1, T2>& p) const {
            size_t lhs = hash<T1>()(p.first), rhs = hash<T2>()(p.second);
            return lhs^(rhs+0x9e3779b9+(lhs<<6)+(lhs>>2));
        }
    };
    BSTNode** vertex_set;
    unordered_map<pair<int, int>, pair<BSTNode*, BSTNode*>, pair_hash> edge_set;
private:
    BSTNode *reroot(BSTNode *ver) noexcept {
        BSTNode *res = splay(ver)->left;
        if(!res) return ver;
        ver->left = nullptr, ver->eval();
        while(ver->right) ver = ver->right;
        splay(ver), ver->right = res, ver->eval(), res->par = ver;
        return ver;
    }
    void link(BSTNode *ver1, BSTNode *ver2, const bool flag) noexcept {
        BSTNode *e1 = new BSTNode(ver1->from, ver2->from, flag);
        BSTNode *e2 = new BSTNode(ver2->from, ver1->from, flag);
        edge_set[{ver1->from, ver2->from}] = {e1, e2};
        join(join(reroot(ver1), e1), join(reroot(ver2), e2));
    }
    void cut(BSTNode *edge1, BSTNode *edge2) noexcept {
        splay(edge1), splay(edge2);
        BSTNode *p = edge1->par;
        bool _right = (edge1 == edge2->right);
        if(p != edge2){
            _right = (p == edge2->right);
            p->par = nullptr, edge1->rotate((edge1 == p->left));
        }
        if(edge1->left) edge1->left->par = nullptr;
        if(edge1->right) edge1->right->par = nullptr;
        if(_right){
            if(edge2->left) edge2->left->par = nullptr;
            join(edge2->left, edge1->right);
        }else{
            if(edge2->right) edge2->right->par = nullptr;
            join(edge1->left, edge2->right);
        }
        delete edge1; delete edge2;
    }
    bool connected(BSTNode *ver1, BSTNode *ver2) noexcept {
        splay(ver1), splay(ver2);
        return ver1->par;
    }
    int component_size(BSTNode *ver) noexcept { return splay(ver)->sz; }
public:
    int V;
    // ~EulerTourTree(){
    //     for(auto it : edge_set){
    //         delete (it.second).first;
    //         delete (it.second).second;
    //     }
    //     for(int i = 0; i < V; ++i) delete vertex_set[i];
    //     delete[] vertex_set;
    // }
    EulerTourTree(const int node_size) noexcept {
        V = node_size, vertex_set = new BSTNode*[V];
        for(int i = 0; i < V; i++) vertex_set[i] = new BSTNode(i);
    }
    void reroot(const int node_id) noexcept { reroot(vertex_set[node_id]); }
    void link(int node1_id, int node2_id, bool flag=true) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        link(vertex_set[node1_id], vertex_set[node2_id], flag);
    }
    void cut(int node1_id, int node2_id){
        if(node1_id > node2_id) swap(node1_id, node2_id);
        auto it = edge_set.find({node1_id, node2_id});
        assert(it != edge_set.end());
        BSTNode *edge1 = (it->second).first, *edge2 = (it->second).second;
        edge_set.erase(it);
        cut(edge1, edge2);
    }
    bool connected(const int node1_id, const int node2_id) noexcept {
        if(node1_id == node2_id) return true;
        return connected(vertex_set[node1_id], vertex_set[node2_id]);
    }
    int component_size(int node_id) noexcept { return component_size(vertex_set[node_id]); }
    void check_dfs(const BSTNode* cur) const noexcept {
        if(cur->left) check_dfs(cur->left);
        cout << "{" << (cur->from) << "," << (cur->to) << "} ";
        if(cur->right) check_dfs(cur->right);
    }
};

// 頂点ノードにそこから出る level i の off tree edge の集合(両方)を乗せる
// 頂点ノードには map でアクセス可能
// link のとき tree edge ならそのまま link
// off tree edge なら level 0 の頂点ノードにそいつを追加
// cut のとき level i の tree edge を level i+1 に押し上げるのだが
// 各 layer の Euler Tree は level i 以上の edge からなるのでその辺が exact に level i の辺であるかをもつ必要がある.
// 基本的に部分木の情報を書き換えるときは(splay してから変更する)根にしてから反映しないとマズい.
// 保持している offtree edge って offtree edge の名の通り常にその両端点は同じ連結成分に属する.

class DynamicConnectivity {
private:
    BSTNode *level_up_dfs(BSTNode *cur, const int layer) noexcept {
        if(cur->exact_level){
            splay(cur)->exact_level = false, cur->subtree_edge_eval();
            detect_layer[{cur->from, cur->to}]++, et[layer+1].link(cur->from, cur->to);
            return cur;
        }
        if(cur->left && cur->left->subtree_edge) return level_up_dfs(cur->left, layer);
        if(cur->right && cur->right->subtree_edge) return level_up_dfs(cur->right, layer);
        return nullptr;
    }
    // another は (u, v) を削除しているけどそのうち larger connected component に属する方
    BSTNode *search_edge_dfs
        (BSTNode *cur, const int layer, const int another, bool& flag, pair<int, int>& rep_edge) noexcept {
        // off_tree edge がある
        if(!cur->adjacent.empty()){
            bool state = et[layer+1].vertex_set[cur->from]->adjacent.empty();
            for(auto it = cur->adjacent.begin(); it != cur->adjacent.end();){
                pair<int, int> e = {min(cur->from, *it), max(cur->from, *it)};
                BSTNode *correspond = et[layer].vertex_set[*it];
                if(et[layer].connected(another, *it)){
                    // another と今見ている先が connected つまり, restore する edge なら
                    flag = true, rep_edge = e;
                    cur->adjacent.erase(it), correspond->adjacent.erase(cur->from);
                    // correspond の方で subofftree フラグが壊れるのを防ぐ.
                    if(!correspond->subofftree_check()){
                        splay(correspond)->subofftree_edge_eval();
                    }
                    break;
                }else{
                    // 今見ている connected component をつなぐ edge なら suboff_tree edge の情報変える
                    if(!et[layer+1].vertex_set[*it]->subofftree_check()){
                        splay(et[layer+1].vertex_set[*it])->subofftree_edge = true;
                    }
                    et[layer+1].vertex_set[cur->from]->adjacent.insert(*it);
                    et[layer+1].vertex_set[*it]->adjacent.insert(cur->from);
                    detect_layer[e]++, it = cur->adjacent.erase(it);
                    correspond->adjacent.erase(cur->from);
                    if(!correspond->subofftree_check()){
                        splay(correspond)->subofftree_edge_eval();
                    }
                }
            }
            // 元々 ajacent が empty で off_tree edge が無かったのに今 cur->from から出る adjacent がある場合
            // でかつ left, right の off_tree edge が false なら subofftree_edge を true にする(これ何してんの？)
            // left もしくは right の off_tree edge が true なら根まで offtree_edge ありますよの伝播がちゃんと行っているので
            // ajacent list が新しく non empty になったけど別に更新する必要はないな
            if(state && !et[layer+1].vertex_set[cur->from]->offtree_check()){
                splay(et[layer+1].vertex_set[cur->from])->subofftree_edge = true;
            }
            splay(cur)->subofftree_edge_eval();
            return cur;
        }
        if(cur->left && cur->left->subofftree_edge){
            return search_edge_dfs(cur->left, layer, another, flag, rep_edge);
        }
        if(cur->right && cur->right->subofftree_edge){
            return search_edge_dfs(cur->right, layer, another, flag, rep_edge);
        }
        return nullptr;
    }
    bool replace(const int from, const int to, const int layer) noexcept {
        if(layer < 0) return true;
        int u, v;
        if(et[layer].component_size(from) <= et[layer].component_size(to)) u = from, v = to;
        else u = to, v = from;
        BSTNode *ver = splay(et[layer].vertex_set[u]);
        // smaller connected component に tree edge があるならそれの level を 1 個上げる
        while(ver->subtree_edge) ver = level_up_dfs(ver, layer);
        pair<int, int> rep_edge = {-1, -1};
        bool flag = false;
        // smaller connected component に offtree edge があり,
        // その辺が連結成分を再びつなげるようなものでないなら 1 増やす
        // flag 見つけたらすぐ終了するという実装にしてるけどそれでもならしが回るので問題ない
        // 1 回探索する度に restore edge が見つかって探索が終了する or 何かの辺の level が 1 上がる が言えるので
        while(ver->subofftree_edge){
            ver = search_edge_dfs(ver, layer, v, flag, rep_edge);
            if(flag) break;
        }
        if(flag){
            // restore edge を見つけたなら
            et[layer].link(rep_edge.first, rep_edge.second);
            for(int i = 0; i < layer; i++) et[i].link(rep_edge.first, rep_edge.second, false);
            return false;
        }else return replace(from, to, layer-1);
    }
public:
    const int V;
    int depth;
    vector<EulerTourTree> et;
    unordered_map<pair<int, int>, int, EulerTourTree::pair_hash> detect_layer;
    DynamicConnectivity(const int node_size) noexcept : V(node_size), depth(1){
        et.emplace_back(V);
    }
    // ~DynamicConnectivity(){
    //     delete[] et;
    // }
    bool link(int node1_id, int node2_id) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        detect_layer[{node1_id, node2_id}] = 0;
        if(et[0].connected(node1_id, node2_id)){
            BSTNode *ver1 = et[0].vertex_set[node1_id], *ver2 = et[0].vertex_set[node2_id];
            splay(ver1)->subofftree_edge = true, ver1->adjacent.insert(node2_id);
            splay(ver2)->subofftree_edge = true, ver2->adjacent.insert(node1_id);
            return false;
        }else{
            et[0].link(node1_id, node2_id);
            return true;
        }
    }
    bool cut(int node1_id, int node2_id) noexcept {
        if(node1_id > node2_id) swap(node1_id, node2_id);
        auto it = detect_layer.find({node1_id, node2_id});
        assert(it != detect_layer.end());
        int layer = it->second;
        detect_layer.erase(it);
        auto& st = et[layer].vertex_set[node1_id]->adjacent;
        if(st.find(node2_id) == st.end()){
            // tree edge の場合
            for(int i = 0; i <= layer; i++) et[i].cut(node1_id, node2_id);
            if(layer + 1 == depth) ++depth, et.emplace_back(V);
            return replace(node1_id, node2_id, layer);
        }else{
            // off_tree edge の場合
            et[layer].vertex_set[node1_id]->adjacent.erase(node2_id);
            // 自分より下に node1_id より下に off tree_edge があるか
            // (もしなかったらこれより上のノードについての off tree edge があるかどうかの情報を書き換える必要がある)
            if(!et[layer].vertex_set[node1_id]->subofftree_check()){
                splay(et[layer].vertex_set[node1_id])->subofftree_edge_eval();
            }
            et[layer].vertex_set[node2_id]->adjacent.erase(node1_id);
            if(!et[layer].vertex_set[node2_id]->subofftree_check()){
                splay(et[layer].vertex_set[node2_id])->subofftree_edge_eval();
            }
            return false;
        }
    }
    bool connected(const int node1_id, const int node2_id) noexcept {
        return et[0].connected(node1_id, node2_id);
    }
};

#define getchar getchar_unlocked
#define putchar putchar_unlocked

inline int in() {
    int n = 0; short c;
    while ((c = getchar()) >= '0') n = n * 10 + c - '0';
    return n;
}

inline void out(int n) {
    short res[10], i = 0;
    do { res[i++] = n % 10, n /= 10; } while (n);
    while (i) putchar(res[--i] + '0');
    putchar('\n');
}

int main()
{
    int n = in(), m = in();
    DynamicConnectivity dc(n);
    rep(i,m){
        int a,b,c;
        a = in(), b = in(), c = in();
        if(a == 1){
            dc.link(b, c);
        }else if(a == 2){
            dc.cut(b, c);
        }else{
            if(dc.connected(b,c)){
                cout << "YES\n";
            }else{
                cout << "NO\n";
            }
        }
    }
    return 0;
}
accuracy: 1
time_ms: 230
memory_kb: 76332
final_rank: 13

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b) for (int i = (int)(a); i < (int)(b); i++)
#define rrep(i, a, b) for (int i = (int)(b)-1; i >= (int)(a); i--)
#define ALL(v) (v).begin(), (v).end()
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define SZ(v) (int)v.size()
#define MIN(v) *min_element(ALL(v))
#define MAX(v) *max_element(ALL(v))
#define LB(v, x) int(lower_bound(ALL(v), (x)) - (v).begin())
#define UB(v, x) int(upper_bound(ALL(v), (x)) - (v).begin())

using uint = unsigned int;
using ll = long long int;
using ull = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;
const int inf = 0x3fffffff;
const ll INF = 0x1fffffffffffffff;

template <typename T> inline bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T> inline bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T, typename U> T ceil(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <typename T, typename U> T floor(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? x / y : (x - y + 1) / y);
}
template <typename T> int popcnt(T x) {
    return __builtin_popcountll(x);
}
template <typename T> int topbit(T x) {
    return (x == 0 ? -1 : 63 - __builtin_clzll(x));
}
template <typename T> int lowbit(T x) {
    return (x == 0 ? -1 : __builtin_ctzll(x));
}

template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << "P(" << p.first << ", " << p.second << ")";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) {
    os << "{";
    for (int i = 0; i < vec.size(); i++) {
        os << vec[i] << (i + 1 == vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
    os << "{";
    for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if (itr != map_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &set_var) {
    os << "{";
    for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
        os << *itr;
        ++itr;
        if (itr != set_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#ifdef LOCAL
#define show(...) _show(0, #__VA_ARGS__, __VA_ARGS__)
#else
#define show(...) true
#endif
template <typename T> void _show(int i, T name) {
    cerr << '\n';
}
template <typename T1, typename T2, typename... T3>
void _show(int i, const T1 &a, const T2 &b, const T3 &...c) {
    for (; a[i] != ',' && a[i] != '\0'; i++)
        cerr << a[i];
    cerr << ":" << b << " ";
    _show(i + 1, a, c...);
}

/**
 * @brief template
 */

struct UnionFindUndo {
  vector< int > data;
  stack< pair< int, int > > history;

  UnionFindUndo(int sz) {
    data.assign(sz, -1);
  }

  bool unite(int x, int y) {
    x = find(x), y = find(y);
    history.emplace(x, data[x]);
    history.emplace(y, data[y]);
    if(x == y) return (false);
    if(data[x] > data[y]) swap(x, y);
    data[x] += data[y];
    data[y] = x;
    return (true);
  }

  int find(int k) {
    if(data[k] < 0) return (k);
    return (find(data[k]));
  }

  int size(int k) {
    return (-data[find(k)]);
  }

  bool same(int x, int y) {return find(x) == find(y);}

  void undo() {
    data[history.top().first] = history.top().second;
    history.pop();
    data[history.top().first] = history.top().second;
    history.pop();
  }

  void snapshot() {
    while(history.size()) history.pop();
  }

  void rollback() {
    while(history.size()) undo();
  }
};

struct OfflineDynamicConnectivity {
  using edge = pair< int, int >;

  UnionFindUndo uf;
  int V, Q, segsz;
  vector< vector< edge > > seg;
  int comp;

  vector< pair< pair< int, int >, edge > > pend;
  map< edge, int > cnt, appear;

  OfflineDynamicConnectivity(int V, int Q) : uf(V), V(V), Q(Q), comp(V) {
    segsz = 1;
    while(segsz < Q) segsz <<= 1;
    seg.resize(2 * segsz - 1);
  }

  void insert(int idx, int s, int t) {
    auto e = minmax(s, t);
    if(cnt[e]++ == 0) appear[e] = idx;
  }

  void erase(int idx, int s, int t) {
    auto e = minmax(s, t);
    if(--cnt[e] == 0) pend.emplace_back(make_pair(appear[e], idx), e);
  }

  void add(int a, int b, const edge &e, int k, int l, int r) {
    if(r <= a || b <= l) return;
    if(a <= l && r <= b) {
      seg[k].emplace_back(e);
      return;
    }
    add(a, b, e, 2 * k + 1, l, (l + r) >> 1);
    add(a, b, e, 2 * k + 2, (l + r) >> 1, r);
  }

  void add(int a, int b, const edge &e) {
    add(a, b, e, 0, 0, segsz);
  }

  void build() {
    for(auto &p : cnt) {
      if(p.second > 0) pend.emplace_back(make_pair(appear[p.first], Q), p.first);
    }
    for(auto &s : pend) {
      add(s.first.first, s.first.second, s.second);
    }
  }

  void run(const function< void(int) > &f, int k = 0) {
    int add = 0;
    for(auto &e : seg[k]) {
      add += uf.unite(e.first, e.second);
    }
    comp -= add;
    if(k < segsz - 1) {
      run(f, 2 * k + 1);
      run(f, 2 * k + 2);
    } else if(k - (segsz - 1) < Q) {
      int query_index = k - (segsz - 1);
      f(query_index);
    }
    for(auto &e : seg[k]) {
      uf.undo();
    }
    comp += add;
  }
};

int main() {
    cin.tie(0);
    ios_base::sync_with_stdio(false);
    int N, Q;
    cin >> N >> Q;
    vector<int> T(Q), U(Q), V(Q);
    rep(i,0,Q) cin >> T[i] >> U[i] >> V[i];
    OfflineDynamicConnectivity UF(N,Q);
    rep(i,0,Q) {
        if (T[i] == 1) UF.insert(i,U[i],V[i]);
        if (T[i] == 2) UF.erase(i,U[i],V[i]);
    }
    UF.build();
    UF.run([&](int k){
        if (T[k] == 3) cout << (UF.uf.same(U[k],V[k]) ? "YES" : "NO") << '\n';
    });
}
accuracy: 1
time_ms: 30
memory_kb: 15620
final_rank: 5

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b) for (int i = (int)(a); i < (int)(b); i++)
#define rrep(i, a, b) for (int i = (int)(b)-1; i >= (int)(a); i--)
#define ALL(v) (v).begin(), (v).end()
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define SZ(v) (int)v.size()
#define MIN(v) *min_element(ALL(v))
#define MAX(v) *max_element(ALL(v))
#define LB(v, x) int(lower_bound(ALL(v), (x)) - (v).begin())
#define UB(v, x) int(upper_bound(ALL(v), (x)) - (v).begin())

using uint = unsigned int;
using ll = long long int;
using ull = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;
const int inf = 0x3fffffff;
const ll INF = 0x1fffffffffffffff;

template <typename T> inline bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T> inline bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T, typename U> T ceil(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <typename T, typename U> T floor(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? x / y : (x - y + 1) / y);
}
template <typename T> int popcnt(T x) {
    return __builtin_popcountll(x);
}
template <typename T> int topbit(T x) {
    return (x == 0 ? -1 : 63 - __builtin_clzll(x));
}
template <typename T> int lowbit(T x) {
    return (x == 0 ? -1 : __builtin_ctzll(x));
}

template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << "P(" << p.first << ", " << p.second << ")";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) {
    os << "{";
    for (int i = 0; i < vec.size(); i++) {
        os << vec[i] << (i + 1 == vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
    os << "{";
    for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if (itr != map_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &set_var) {
    os << "{";
    for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
        os << *itr;
        ++itr;
        if (itr != set_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#ifdef LOCAL
#define show(...) _show(0, #__VA_ARGS__, __VA_ARGS__)
#else
#define show(...) true
#endif
template <typename T> void _show(int i, T name) {
    cerr << '\n';
}
template <typename T1, typename T2, typename... T3>
void _show(int i, const T1 &a, const T2 &b, const T3 &...c) {
    for (; a[i] != ',' && a[i] != '\0'; i++)
        cerr << a[i];
    cerr << ":" << b << " ";
    _show(i + 1, a, c...);
}

/**
 * @brief template
 */

struct RollbackUnionFind{
    vector<int> par;
    stack<pair<int,int>> history;
    int snap;
    RollbackUnionFind(){}
    RollbackUnionFind(int _n):par(_n,-1),snap(0){}
    int root(int x){return par[x]<0?x:par[x]=root(par[x]);}
    bool same(int x,int y){return root(x)==root(y);}
    int size(int x){return -par[root(x)];}
    bool unite(int x,int y){
        x=root(x),y=root(y);
        history.push({x,par[x]});
        history.push({y,par[y]});
        if(x==y)return false;
        if(size(x)>size(y))swap(x,y);
        par[y]+=par[x]; par[x]=y;
        return true;
    }
    void undo(){
        par[history.top().first]=history.top().second;
        history.pop();
        par[history.top().first]=history.top().second;
        history.pop();
    }
    void snapshot(){snap=int(history.size()>>1);}
    int get(){return int(history.size()>>1);}
    void rollback(int state=-1){
        if(state==-1)state=snap;
        state<<=1;
        while(state<history.size())undo();
    }
};

/**
 * @brief Rollback Union Find
 */

struct OfflineDynamicConnectivity {
    using edge = pair<int,int>;

    RollbackUnionFind UF;
    int V, Q, segsz;
    vector<vector<edge>> seg;
    int comp;

    vector<pair<pair<int,int>,edge>> pend;
    map<edge,int> cnt, appear;

    OfflineDynamicConnectivity(int V, int Q) : UF(V), V(V), Q(Q), comp(V) {
        segsz = 1;
        while(segsz < Q) segsz <<= 1;
        seg.resize(2 * segsz - 1);
    }

    void insert(int idx, int s, int t) {
        auto e = minmax(s,t);
        if (cnt[e]++ == 0) appear[e] = idx;
    }

    void erase(int idx, int s, int t) {
        auto e = minmax(s,t);
        if (--cnt[e] == 0) pend.push_back({{appear[e], idx}, e});
    }

    void add(int a, int b, const edge& e, int k, int l, int r) {
        if (r <= a || b <= l) return;
        if (a <= l && r <= b) {
            seg[k].emplace_back(e);
            return;
        }
        add(a,b,e,2*k+1,l,(l+r)>>1);
        add(a,b,e,2*k+2,(l+r)>>1,r);
    }

    void add(int a, int b, const edge& e) {
        add(a,b,e,0,0,segsz);
    }

    void build() {
        for (auto& p : cnt) {
            if (p.second > 0) pend.push_back({{appear[p.first], Q}, p.first});
        }
        for (auto& s : pend) {
            add(s.first.first, s.first.second, s.second);
        }
    }

    void run(const function<void(int)>& f, int k = 0) {
        int add = 0;
        for (auto& e : seg[k]) {
            add += UF.unite(e.first, e.second);
        }
        comp -= add;
        if (k < segsz - 1) {
            run(f, 2*k+1);
            run(f, 2*k+2);
        }
        else if (k-(segsz-1) < Q) {
            int query_index = k - (segsz - 1);
            f(query_index);
        }
        for (auto& e : seg[k]) {
            UF.undo();
        }
        comp += add;
    }
};

int main() {
    cin.tie(0);
    ios_base::sync_with_stdio(false);
    int N, Q;
    cin >> N >> Q;
    vector<int> T(Q), U(Q), V(Q);
    rep(i,0,Q) cin >> T[i] >> U[i] >> V[i];
    OfflineDynamicConnectivity UF(N,Q);
    rep(i,0,Q) {
        if (T[i] == 1) UF.insert(i,U[i],V[i]);
        if (T[i] == 2) UF.erase(i,U[i],V[i]);
    }
    UF.build();
    UF.run([&](int k){
        if (T[k] == 3) cout << (UF.UF.same(U[k],V[k]) ? "YES" : "NO") << '\n';
    });
}
accuracy: 0.13157894736842105
time_ms: 20
memory_kb: 9568
final_rank: 20

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b) for (int i = (int)(a); i < (int)(b); i++)
#define rrep(i, a, b) for (int i = (int)(b)-1; i >= (int)(a); i--)
#define ALL(v) (v).begin(), (v).end()
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define SZ(v) (int)v.size()
#define MIN(v) *min_element(ALL(v))
#define MAX(v) *max_element(ALL(v))
#define LB(v, x) int(lower_bound(ALL(v), (x)) - (v).begin())
#define UB(v, x) int(upper_bound(ALL(v), (x)) - (v).begin())

using uint = unsigned int;
using ll = long long int;
using ull = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;
const int inf = 0x3fffffff;
const ll INF = 0x1fffffffffffffff;

template <typename T> inline bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T> inline bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T, typename U> T ceil(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <typename T, typename U> T floor(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? x / y : (x - y + 1) / y);
}
template <typename T> int popcnt(T x) {
    return __builtin_popcountll(x);
}
template <typename T> int topbit(T x) {
    return (x == 0 ? -1 : 63 - __builtin_clzll(x));
}
template <typename T> int lowbit(T x) {
    return (x == 0 ? -1 : __builtin_ctzll(x));
}

template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << "P(" << p.first << ", " << p.second << ")";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) {
    os << "{";
    for (int i = 0; i < vec.size(); i++) {
        os << vec[i] << (i + 1 == vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
    os << "{";
    for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if (itr != map_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &set_var) {
    os << "{";
    for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
        os << *itr;
        ++itr;
        if (itr != set_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#ifdef LOCAL
#define show(...) _show(0, #__VA_ARGS__, __VA_ARGS__)
#else
#define show(...) true
#endif
template <typename T> void _show(int i, T name) {
    cerr << '\n';
}
template <typename T1, typename T2, typename... T3>
void _show(int i, const T1 &a, const T2 &b, const T3 &...c) {
    for (; a[i] != ',' && a[i] != '\0'; i++)
        cerr << a[i];
    cerr << ":" << b << " ";
    _show(i + 1, a, c...);
}

/**
 * @brief template
 */

struct UnionFind{
    vector<int> par; int n;
    UnionFind(){}
    UnionFind(int _n):par(_n,-1),n(_n){}
    int root(int x){return par[x]<0?x:par[x]=root(par[x]);}
    bool same(int x,int y){return root(x)==root(y);}
    int size(int x){return -par[root(x)];}
    bool unite(int x,int y){
        x=root(x),y=root(y); if(x==y)return false;
        if(size(x)>size(y))swap(x,y);
        par[y]+=par[x]; par[x]=y; n--; return true;
    }
};

/**
 * @brief Union Find
 */

int main() {
    cin.tie(0);
    ios_base::sync_with_stdio(false);
    int N, Q, M;
    cin >> N >> Q;
    int B = 200;
    vector<int> T(Q), U(Q), V(Q), ID(Q, -1);
    vector<int> IDtoU, IDtoV;
    rep(i,0,Q) cin >> T[i] >> U[i] >> V[i];
    {
        map<pair<int,int>,int> mp;
        int cur = 0;
        rep(i,0,Q) {
            if (T[i] == 3) continue;
            if (mp.count({U[i],V[i]})) ID[i] = mp[{U[i],V[i]}];
            else {
                IDtoU.push_back(U[i]);
                IDtoV.push_back(V[i]);
                ID[i] = cur;
                mp[{U[i],V[i]}] = cur;
                cur++;
            }
        }
        M = cur;
    }
    rep(i,0,ceil(Q,B)) {
        vector<bool> edges(M,false);
        rep(j,0,i*B) {
            if (T[j] == 1) edges[ID[j]] = true;
            if (T[j] == 2) edges[ID[j]] = false;
        }
        vector<bool> willuse(M,false);
        rep(j,i*B,min(Q,(i+1)*B)) {
            if (T[j] == 3) continue;
            assert(ID[j] >= 0);
            willuse[ID[j]] = true;
        }
        vector<int> willuses;
        rep(j,0,M) if (willuse[j]) willuses.push_back(j);
        UnionFind UF1(N);
        rep(j,0,M) {
            if (edges[j] && !willuse[j]) UF1.unite(IDtoU[j], IDtoV[j]);
        }
        vector<bool> isuse(N,false);
        rep(j,i*B,min(Q,(i+1)*B)) {
            isuse[UF1.root(U[j])] = true;
            isuse[UF1.root(V[j])] = true;
        }
        vector<int> VID(N,-1);
        int NewM;
        {
            int cur = 0;
            rep(j,0,N) {
                if (isuse[j]) {
                    VID[j] = cur;
                    cur++;
                }
            }
            NewM = cur;
        }
        rep(j,i*B,min(Q,(i+1)*B)) {
            if (T[j] == 1) {
                edges[ID[j]] = true;
            }
            if (T[j] == 2) {
                edges[ID[j]] = false;
            }
            if (T[j] == 3) {
                UnionFind UF2(NewM);
                for (int k : willuses) {
                    if (edges[k]) {
                        int u = IDtoU[k], v = IDtoV[k];
                        u = UF1.root(u), v = UF1.root(v);
                        u = VID[u], v = VID[v];
                        UF2.unite(u,v);
                    }
                }
                {
                    int u = U[j], v = V[j];
                    u = UF1.root(u), v = UF1.root(v);
                    u = VID[u], v = VID[v];
                    cout << (UF2.same(u,v) ? "YES" : "NO") << "\n";
                }
            }
        }
    }
}
accuracy: 1
time_ms: 60
memory_kb: 6104
final_rank: 6

code_snippet: #line 1 "/home/dispersion/competitive/icpc/ICPC-library/ICPC2024/merge_src_docs/template/24_template.hpp"
#include <bits/stdc++.h>
using namespace std;

using ll = long long;
#define rep(i, n) for(ll i = 0; i < n; i++)
#define rep2(i, l, r) for(ll i = l; i < r; i++)

using vi = vector<int>;
using vvi = vector<vi>;
using vll = vector<ll>;

#define all(A) A.begin(), A.end()
#define elif else if
using pii = pair<ll, ll>;

bool chmin(auto &a, auto b) { return a > b ? a = b, 1 : 0; }
bool chmax(auto &a, auto b) { return a < b ? a = b, 1 : 0; }

struct IOSetup {
  IOSetup() {
    cin.tie(0);
    ios::sync_with_stdio(0);
  }
} iosetup;

template<class T>
void print(vector<T> a) {
  for(auto x : a) cout << x << ' ';
  cout << endl;
}

void print(auto x) { cout << x << endl; }

template<class Head, class... Tail>
void print(Head &&head, Tail &&...tail) {
  cout << head << ' ';
  print(forward<Tail>(tail)...);
}
#line 1 "/home/dispersion/competitive/icpc/ICPC-library/ICPC2024/merge_src_docs/graph/OffDyCon.hpp"
/*
OfflineDynamicConnectivity(int N, int Q) : 頂点数 N のグラフ、Q 個のクエリで初期化
insert(int idx, int s, int t) : 時刻 idx に辺 s-t を追加する
erase(int idx, int s, int t) : 時刻 idx に辺 s-t を削除する
build() : 追加・削除クエリを与えたのちに呼び出す
run(function<void(int)> f) : 
  build() 後に呼び出すことで、各時刻 t = [0, Q) について f(t) が呼び出される
  O(Q log(Q) log(N)) time
*/

#line 1 "/home/dispersion/competitive/icpc/ICPC-library/ICPC2024/merge_src_docs/data_structure/RollbackUnionFind.hpp"
struct RollbackUnionFind {
  vector<int> data;
  stack<pair<int, int>> history;

  RollbackUnionFind(int sz) { data.assign(sz, -1); }

  bool unite(int x, int y) {
    x = find(x), y = find(y);
    history.emplace(x, data[x]);
    history.emplace(y, data[y]);
    if(x == y) return (false);
    if(data[x] > data[y]) swap(x, y);
    data[x] += data[y];
    data[y] = x;
    return (true);
  }

  int find(int k) {
    if(data[k] < 0) return (k);
    return (find(data[k]));
  }

  bool same(int x, int y) { return find(x) == find(y); }

  int size(int k) { return (-data[find(k)]); }

  void undo() {
    data[history.top().first] = history.top().second;
    history.pop();
    data[history.top().first] = history.top().second;
    history.pop();
  }

  void snapshot() {
    while(history.size()) history.pop();
  }

  void rollback() {
    while(history.size()) undo();
  }
};
#line 12 "/home/dispersion/competitive/icpc/ICPC-library/ICPC2024/merge_src_docs/graph/OffDyCon.hpp"
struct OffDyCon {
  using edge = pair<int, int>;

  RollbackUnionFind uf;
  int V, Q, segsz;
  vector<vector<edge>> seg;
  int comp;

  vector<pair<pair<int, int>, edge>> pend;
  map<edge, int> cnt, appear;

  OffDyCon(int V, int Q) : uf(V), V(V), Q(Q), comp(V) {
    segsz = 1;
    while(segsz < Q) segsz <<= 1;
    seg.resize(2 * segsz - 1);
  }

  void insert(int idx, int s, int t) {
    auto e = minmax(s, t);
    if(cnt[e]++ == 0) appear[e] = idx;
  }

  void erase(int idx, int s, int t) {
    auto e = minmax(s, t);
    if(--cnt[e] == 0) pend.emplace_back(make_pair(appear[e], idx), e);
  }

  void add(int a, int b, const edge &e, int k, int l, int r) {
    if(r <= a || b <= l) return;
    if(a <= l && r <= b) {
      seg[k].emplace_back(e);
      return;
    }
    add(a, b, e, 2 * k + 1, l, (l + r) >> 1);
    add(a, b, e, 2 * k + 2, (l + r) >> 1, r);
  }

  void add(int a, int b, const edge &e) { add(a, b, e, 0, 0, segsz); }

  void build() {
    for(auto &p : cnt) {
      if(p.second > 0)
        pend.emplace_back(make_pair(appear[p.first], Q), p.first);
    }
    for(auto &s : pend) { add(s.first.first, s.first.second, s.second); }
  }

  void run(const function<void(int)> &f, int k = 0) {
    int add = 0;
    for(auto &e : seg[k]) { add += uf.unite(e.first, e.second); }
    comp -= add;
    if(k < segsz - 1) {
      run(f, 2 * k + 1);
      run(f, 2 * k + 2);
    }
    else if(k - (segsz - 1) < Q) {
      int query_index = k - (segsz - 1);
      f(query_index);
    }
    for(auto &e : seg[k]) { uf.undo(); }
    comp += add;
  }
};
#line 3 "OffDyCon.cpp"

// https://onlinejudge.u-aizu.ac.jp/services/ice/?problemId=2235
void solve1() {
  ll N, Q; cin >> N >> Q;
  
  OffDyCon dycon(N, Q);
  vector<array<ll, 2>> query(Q);
  vector<ll> is3(Q, 0);
  
  rep(q, Q) {
    ll t, u, v; cin >> t >> u >> v;
    query[q] = {u, v};
    
    if(t == 1) dycon.insert(q, u, v);
    else if(t == 2) dycon.erase(q, u, v);
    else if(t == 3) is3[q] = 1;
  }
  dycon.build();
  
  vector<ll> res(Q, 0);
  auto func = [&](ll t) -> void {
    auto [u, v] = query[t];
    res[t] = dycon.uf.same(u, v);
    return;
  };
  dycon.run(func);
  
  rep(i, Q) {
    if(is3[i]) cout << (res[i] ? "YES" : "NO") << '\n';
  }
  
  return;
}

int main() {
  solve1();
  
  return 0;
}
accuracy: 1
time_ms: 30
memory_kb: 16684
final_rank: 7

code_snippet: #pragma region Macros
#include <bits/stdc++.h>

using namespace std;
using lint = long long;
using ull = unsigned long long;
using ld = long double;
using int128 = __int128_t;
#define all(x) (x).begin(), (x).end()
#define EPS 1e-8
#define uniqv(v) v.erase(unique(all(v)), v.end())
#define OVERLOAD_REP(_1, _2, _3, name, ...) name
#define REP1(i, n) for (auto i = std::decay_t<decltype(n)>{}; (i) != (n); ++(i))
#define REP2(i, l, r) for (auto i = (l); (i) != (r); ++(i))
#define rep(...) OVERLOAD_REP(__VA_ARGS__, REP2, REP1)(__VA_ARGS__)
#define log(x) cout << x << endl
#define logfixed(x) cout << fixed << setprecision(10) << x << endl;
#define logy(bool)         \
  if (bool) {              \
    cout << "Yes" << endl; \
  } else {                 \
    cout << "No" << endl;  \
  }

ostream &operator<<(ostream &dest, __int128_t value) {
  ostream::sentry s(dest);
  if (s) {
    __uint128_t tmp = value < 0 ? -value : value;
    char buffer[128];
    char *d = end(buffer);
    do {
      --d;
      *d = "0123456789"[tmp % 10];
      tmp /= 10;
    } while (tmp != 0);
    if (value < 0) {
      --d;
      *d = '-';
    }
    int len = end(buffer) - d;
    if (dest.rdbuf()->sputn(d, len) != len) {
      dest.setstate(ios_base::badbit);
    }
  }
  return dest;
}

template <typename T>
ostream &operator<<(ostream &os, const vector<T> &v) {
  for (int i = 0; i < (int)v.size(); i++) {
    os << v[i] << (i + 1 != (int)v.size() ? " " : "");
  }
  return os;
}

template <typename T>
ostream &operator<<(ostream &os, const set<T> &set_var) {
  for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
    os << *itr;
    ++itr;
    if (itr != set_var.end()) os << " ";
    itr--;
  }
  return os;
}

template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
  for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
    os << itr->first << " -> " << itr->second << "\n";
  }
  return os;
}

template <typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &pair_var) {
  os << "(" << pair_var.first << ", " << pair_var.second << ")";
  return os;
}

void out() { cout << '\n'; }
template <class T, class... Ts>
void out(const T &a, const Ts &...b) {
  cout << a;
  (cout << ... << (cout << ' ', b));
  cout << '\n';
}

template <typename T>
istream &operator>>(istream &is, vector<T> &v) {
  for (T &in : v) is >> in;
  return is;
}

inline void in(void) { return; }
template <typename First, typename... Rest>
void in(First &first, Rest &...rest) {
  cin >> first;
  in(rest...);
  return;
}

template <typename T>
bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <typename T>
bool chmin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}

vector<lint> dx8 = {1, 1, 0, -1, -1, -1, 0, 1};
vector<lint> dy8 = {0, 1, 1, 1, 0, -1, -1, -1};
vector<lint> dx4 = {1, 0, -1, 0};
vector<lint> dy4 = {0, 1, 0, -1};

#pragma endregion

template <class S, auto op, auto e>
class UndoableUnionFind {
 private:
  struct HistoryData {
    int u, datu;
    S accu;
    int v, datv;
    S accv;
    int comp_cnt;
  };

  vector<int> data;
  vector<S> acc;
  stack<HistoryData> history;
  int cnt;
  int snap;

 public:
  UndoableUnionFind() {}
  UndoableUnionFind(int sz) {
    data.assign(sz, -1);
    cnt = sz;
    acc.resize(sz, e());
  }

  bool unite(int u, int v) {
    u = find(u), v = find(v);
    history.emplace(u, data[u], acc[u], v, data[v], acc[v], cnt);
    if (u == v) return false;
    if (data[u] > data[v]) swap(u, v);
    data[u] += data[v];
    data[v] = u;
    acc[u] = op(acc[u], acc[v]);
    cnt--;
    return true;
  }

  int find(int k) {
    while (data[k] >= 0) {
      k = data[k];
    }
    return k;
  }

  void update(int a, S x) {
    a = find(a);
    history.push({a, data[a], acc[a], -1, -1, e(), -1});
    acc[a] = op(acc[a], x);
  }

  lint prod_components(int a) {
    return acc[find(a)];
  }

  bool same(int x, int y) { return find(x) == find(y); }

  int size(int k) { return (-data[find(k)]); }

  void undo() {
    HistoryData h = history.top();
    history.pop();
    data[h.u] = h.datu;
    acc[h.u] = h.accu;
    if (h.v != -1) {
      data[h.v] = h.datv;
      acc[h.v] = h.accv;
      cnt = h.comp_cnt;
    }
  }

  int components() {
    return cnt;
  }
};

template <class S, auto op, auto e>
struct OfflineDynamicConnectivity {
 private:
  struct Edge {
    int from, to;
  };

  struct Query {
    int com;
    int u, v;
    int start;
    int finish;
    lint val;
  };
  vector<Query> q;
  vector<S> res;
  int outq = 0;
  int n;
  vector<unordered_map<int, int>> ed;
  vector<vector<Edge>> node;
  vector<vector<pair<int, S>>> updates;
  vector<int> val_idx;
  UndoableUnionFind<S, op, e> d;
  int vertex_siz;
  int qtime;
  int Q_INF = 1e8;
  int idx = 0;

  vector<vector<Query>> outputQuery;

  void add(int a, int b, Edge &ed, int k = 0, int l = 0, int r = -1) {
    if (r < 0) r = n;
    if (r <= a || b <= l) return;
    if (a <= l && r <= b) {
      node[k].emplace_back(ed);
      return;
    }
    add(a, b, ed, 2 * k + 1, l, (l + r) / 2);
    add(a, b, ed, 2 * k + 2, (l + r) / 2, r);
  }

  void add_update(int a, int b, pair<int, S> x, int k = 0, int l = 0, int r = -1) {
    if (r < 0) r = n;
    if (r <= a || b <= l) return;
    if (a <= l && r <= b) {
      updates[k].emplace_back(x);
      return;
    }
    add_update(a, b, x, 2 * k + 1, l, (l + r) / 2);
    add_update(a, b, x, 2 * k + 2, (l + r) / 2, r);
  }

  void execute(int k = 0) {
    if (outq == 0) return;
    for (auto &ed : node[k]) {
      d.unite(ed.from, ed.to);
    }

    for (auto &p : updates[k]) {
      d.update(p.first, p.second);
    }

    if (k < n - 1) {
      execute(2 * k + 1);
      execute(2 * k + 2);
    } else if (k - (n - 1) < qtime) {
      int qidx = k - (n - 1);
      for (auto cur : outputQuery[qidx]) {
        int com = cur.com;
        int u = cur.u;
        int v = cur.v;
        if (com == 2) {
          res.emplace_back(d.same(u, v));
        } else if (com == 3) {
          res.emplace_back(d.components());
        } else if (com == 4) {
          res.emplace_back(d.size(u));
        } else if (com == 6) {
          res.emplace_back(d.prod_components(u));
        }
      }
    }
    for (int i = 0; i < int(node[k].size() + updates[k].size()); i++) {
      d.undo();
    }
  }

 public:
  OfflineDynamicConnectivity(int siz) {
    d = UndoableUnionFind<S, op, e>(siz);
    vertex_siz = siz;
    ed.resize(siz);
    val_idx.resize(siz, -1);
  }

  void link(int u, int v) {
    if (u > v) swap(u, v);
    if (ed[u].find(v) != ed[u].end()) return;
    qtime++;
    q.push_back({0, u, v, qtime, Q_INF, 0});
    ed[u][v] = idx;
    idx++;
  }

  void cut(int u, int v) {
    if (u > v) swap(u, v);
    qtime++;
    q.push_back({1, u, v, qtime, -1, 0});
    int pos = ed[u][v];
    q[pos].finish = qtime;
    ed[u].erase(v);
    idx++;
  }

  void update(int u, lint x) {
    qtime++;
    q.push_back({5, u, -1, qtime, Q_INF, x});
    idx++;
  }

  void is_connected(int u, int v) {
    if (u > v) swap(u, v);
    q.push_back({2, u, v, qtime, -1, 0});
    idx++;
    outq++;
  }

  void components() {
    q.push_back({3, -1, -1, qtime, -1, 0});
    idx++;
    outq++;
  }

  void size(int u) {
    q.push_back({4, u, -1, qtime, -1, 0});
    idx++;
    outq++;
  }

  void prod(int u) {
    q.push_back({6, u, -1, qtime, -1, 0});
    idx++;
    outq++;
  }

  vector<S> build() {
    qtime++;

    int sz = qtime;
    n = 1;
    while (n < sz) n *= 2;
    node.resize(2 * n - 1);
    updates.resize(2 * n - 1);
    outputQuery.resize(qtime);
    for (int i = 0; i < q.size(); i++) {
      if (q[i].com == 0) {
        Edge ed = {q[i].u, q[i].v};
        add(q[i].start, min(q[i].finish, qtime), ed);
      } else if (q[i].com == 5) {
        add_update(q[i].start, q[i].finish, {q[i].u, q[i].val});
      } else if (q[i].com != 1) {
        outputQuery[q[i].start].emplace_back(q[i]);
      }
    }
    execute();
    return res;
  }
};

using S = lint;

S op(S a, S b) {
  return a + b;
}

S e() {
  return 0;
}

int main() {
  int n, q;
  in(n, q);
  OfflineDynamicConnectivity<S, op, e> g(n);


  rep(i, q) {
    int com, u, v;
    in(com,u,v);
    if(com==1){
        g.link(u,v);
    }else if(com==2){
        g.cut(u,v);
    }else if(com==3){
        g.is_connected(u,v);
    }
  }
  vector<S> res = g.build();
  for (S x : res) {
    out(x?"YES":"NO");
  }
  return 0;
}
accuracy: 1
time_ms: 20
memory_kb: 20440
final_rank: 8

code_snippet: #include <bits/stdc++.h>
using namespace std;

struct UndoableUnionFind {
    private:
    int n;
    vector<int> par;
    stack<pair<int, int>> history;
    
    public:
    UndoableUnionFind() {}
    UndoableUnionFind(int n_) : n(n_), par(n, -1) {}
    
    int merge(int a, int b) {
        assert(0 <= a && a < n);
        assert(0 <= b && b < n);
        int x = leader(a), y = leader(b);
        history.emplace(x, par[x]);
        history.emplace(y, par[y]);
        if (x == y) {
            return x;
        }
        if (-par[x] < -par[y]) {
            swap(x, y);
        }
        par[x] += par[y];
        par[y] = x;
        return x;
    }
    
    bool same(int a, int b) {
        assert(0 <= a && a < n);
        assert(0 <= b && b < n);
        return leader(a) == leader(b);
    }
    
    int leader(int a) {
        assert(0 <= a && a < n);
        if (par[a] < 0) {
            return a;
        }
        return leader(par[a]);
    }
    
    int size(int a) {
        assert(0 <= a && a < n);
        return -par[leader(a)];
    }
    
    void undo() {
        if (history.empty()) {
            return;
        }
        par[history.top().first] = history.top().second;
        history.pop();
        par[history.top().first] = history.top().second;
        history.pop();
    }
    
    vector<vector<int>> groups() {
        vector<vector<int>> member(n);
        for (int i = 0; i < n; i++) {
            member[leader(i)].emplace_back(i);
        }
        vector<vector<int>> res;
        for (int i = 0; i < n; i++) {
            if ((int)member[i].size() > 0) {
                res.emplace_back(member[i]);
            }
        }
        return res;
    }
};

struct OfflineDynamicConnectivity {
    using edge = pair<int, int>;
    int n, q, sz;
    vector<vector<edge>> seg;
    UndoableUnionFind uf;
    map<edge, int> cnt, appear;
    vector<pair<edge, pair<int, int>>> times;
    
    OfflineDynamicConnectivity() {}
    OfflineDynamicConnectivity(int n_, int q_) : n(n_), q(q_), sz(1), uf(n) {
        while (sz < q) {
            sz *= 2;
        }
        seg.resize(sz * 2);
    }
    
    void insert(int t, int u, int v) {
        assert(1 <= t);
        assert(0 <= u && u < n);
        assert(0 <= v && v < n);
        if (u > v) {
            swap(u, v);
        }
        edge e = {u, v};
        if (cnt[e] == 0) {
            cnt[e]++;
            appear[e] = t;
        }
    }
    
    void erase(int t, int u, int v) {
        assert(1 <= t);
        assert(0 <= u && u < n);
        assert(0 <= v && v < n);
        if (u > v) {
            swap(u, v);
        }
        edge e = {u, v};
        if (cnt[e] == 1) {
            cnt[e]--;
            pair<int, int> lr = {appear[e], t};
            times.emplace_back(e, lr);
        }
    }
    
    void add(edge e, int l, int r) {
        l += sz;
        r += sz;
        while (l < r) {
            if (l & 1) {
                seg[l].emplace_back(e);
                l++;
            }
            if (r & 1) {
                r--;
                seg[r].emplace_back(e);
            }
            l >>= 1;
            r >>= 1;
        }
    }
    
    void build() {
        for (auto [e, x] : cnt) {
            if (x > 0) {
                pair<int, int> lr = {appear[e], q};
                times.emplace_back(e, lr);
            }
        }
        for (auto [e, lr] : times) {
            auto [l, r] = lr;
            add(e, l, r);
        }
    }
    
    void run(function<void(int)> f, int i = 1) {
        for (auto e : seg[i]) {
            auto [u, v] = e;
            uf.merge(u, v);
        }
        if (0 <= i - sz) {
            if (i - sz < q) {
                f(i - sz);
            }
        } else {
            run(f, i * 2);
            run(f, i * 2 + 1);
        }
        for (int j = 0; j < (int)seg[i].size(); j++) {
            uf.undo();
        }
    }
};

int main() {
    int n, k;
    cin >> n >> k;
    OfflineDynamicConnectivity dc(n, k + 1);
    vector<int> t(k + 1), u(k + 1), v(k + 1);
    for (int i = 1; i <= k; i++) {
        cin >> t[i] >> u[i] >> v[i];
        if (t[i] == 1) {
            dc.insert(i, u[i], v[i]);
        } else if (t[i] == 2) {
            dc.erase(i, u[i], v[i]);
        }
    }
    dc.build();
    vector<int> ans(k + 1, -1);
    auto f = [&](int i) -> void {
        if (t[i] == 3) {
            ans[i] = dc.uf.same(u[i], v[i]);
        }
    };
    dc.run(f);
    for (int i = 1; i <= k; i++) {
        if (ans[i] != -1) {
            cout << (ans[i] ? "YES" : "NO") << endl;
        }
    }
}
accuracy: 1
time_ms: 40
memory_kb: 15452
final_rank: 9

code_snippet: #include <bits/stdc++.h>
using namespace std;

struct UndoableUnionFind {
    private:
    int n;
    vector<int> par;
    stack<pair<int, int>> history;
    
    public:
    UndoableUnionFind() {}
    UndoableUnionFind(int n_) : n(n_), par(n, -1) {}
    
    int merge(int a, int b) {
        assert(0 <= a && a < n);
        assert(0 <= b && b < n);
        int x = leader(a), y = leader(b);
        history.emplace(x, par[x]);
        history.emplace(y, par[y]);
        if (x == y) {
            return x;
        }
        if (-par[x] < -par[y]) {
            swap(x, y);
        }
        par[x] += par[y];
        par[y] = x;
        return x;
    }
    
    bool same(int a, int b) {
        assert(0 <= a && a < n);
        assert(0 <= b && b < n);
        return leader(a) == leader(b);
    }
    
    int leader(int a) {
        assert(0 <= a && a < n);
        if (par[a] < 0) {
            return a;
        }
        return leader(par[a]);
    }
    
    int size(int a) {
        assert(0 <= a && a < n);
        return -par[leader(a)];
    }
    
    void undo() {
        if (history.empty()) {
            return;
        }
        par[history.top().first] = history.top().second;
        history.pop();
        par[history.top().first] = history.top().second;
        history.pop();
    }
    
    vector<vector<int>> groups() {
        vector<vector<int>> member(n);
        for (int i = 0; i < n; i++) {
            member[leader(i)].emplace_back(i);
        }
        vector<vector<int>> res;
        for (int i = 0; i < n; i++) {
            if ((int)member[i].size() > 0) {
                res.emplace_back(member[i]);
            }
        }
        return res;
    }
};

struct OfflineDynamicConnectivity {
    using edge = pair<int, int>;
    int n, q, sz;
    vector<vector<edge>> seg;
    UndoableUnionFind uf;
    map<edge, int> cnt, appear;
    vector<pair<edge, pair<int, int>>> times;
    
    OfflineDynamicConnectivity() {}
    OfflineDynamicConnectivity(int n_, int q_) : n(n_), q(q_), sz(1), uf(n) {
        while (sz < q) {
            sz *= 2;
        }
        seg.resize(sz * 2);
    }
    
    void insert(int t, int u, int v) {
        assert(1 <= t);
        assert(0 <= u && u < n);
        assert(0 <= v && v < n);
        if (u > v) {
            swap(u, v);
        }
        edge e = {u, v};
        if (cnt[e] == 0) {
            cnt[e]++;
            appear[e] = t;
        }
    }
    
    void erase(int t, int u, int v) {
        assert(1 <= t);
        assert(0 <= u && u < n);
        assert(0 <= v && v < n);
        if (u > v) {
            swap(u, v);
        }
        edge e = {u, v};
        if (cnt[e] == 1) {
            cnt[e]--;
            pair<int, int> lr = {appear[e], t};
            times.emplace_back(e, lr);
        }
    }
    
    void add(edge e, int l, int r) {
        l += sz;
        r += sz;
        while (l < r) {
            if (l & 1) {
                seg[l].emplace_back(e);
                l++;
            }
            if (r & 1) {
                r--;
                seg[r].emplace_back(e);
            }
            l >>= 1;
            r >>= 1;
        }
    }
    
    void build() {
        for (auto [e, x] : cnt) {
            if (x > 0) {
                pair<int, int> lr = {appear[e], q};
                times.emplace_back(e, lr);
            }
        }
        for (auto [e, lr] : times) {
            auto [l, r] = lr;
            add(e, l, r);
        }
    }
    
    void run(function<void(int)> f, int i = 1) {
        for (auto e : seg[i]) {
            auto [u, v] = e;
            uf.merge(u, v);
        }
        if (i >= sz) {
            f(i - sz);
        } else {
            run(f, i * 2);
            run(f, i * 2 + 1);
        }
        for (int j = 0; j < (int)seg[i].size(); j++) {
            uf.undo();
        }
    }
};

int main() {
    int n, k;
    cin >> n >> k;
    OfflineDynamicConnectivity dc(n, k + 1);
    vector<int> t(k + 1), u(k + 1), v(k + 1);
    for (int i = 1; i <= k; i++) {
        cin >> t[i] >> u[i] >> v[i];
        if (t[i] == 1) {
            dc.insert(i, u[i], v[i]);
        } else if (t[i] == 2) {
            dc.erase(i, u[i], v[i]);
        }
    }
    dc.build();
    vector<int> ans(k + 1, -1);
    auto f = [&](int i) -> void {
        if (t[i] == 3) {
            ans[i] = dc.uf.same(u[i], v[i]);
        }
    };
    dc.run(f);
    for (int i = 1; i <= k; i++) {
        if (ans[i] != -1) {
            cout << (ans[i] ? "YES" : "NO") << endl;
        }
    }
}
accuracy: 0.4473684210526316
time_ms: 30
memory_kb: 9504
final_rank: 18

code_snippet: #include <bits/stdc++.h>
using namespace std;
#if __has_include(<atcoder/all>)
#include <atcoder/all>
#endif
using ll=long long;
using ull=unsigned long long;
using P=pair<ll,ll>;
template<typename T>using minque=priority_queue<T,vector<T>,greater<T>>;
template<typename T>bool chmax(T &a,const T &b){return (a<b?(a=b,true):false);}
template<typename T>bool chmin(T &a,const T &b){return (a>b?(a=b,true):false);}
template<typename T1,typename T2>istream &operator>>(istream &is,pair<T1,T2>&p){is>>p.first>>p.second;return is;}
template<typename T>istream &operator>>(istream &is,vector<T> &a){for(auto &i:a)is>>i;return is;}
template<typename T1,typename T2>void operator++(pair<T1,T2>&a,int n){a.first++,a.second++;}
template<typename T1,typename T2>void operator--(pair<T1,T2>&a,int n){a.first--,a.second--;}
template<typename T>void operator++(vector<T>&a,int n){for(auto &i:a)i++;}
template<typename T>void operator--(vector<T>&a,int n){for(auto &i:a)i--;}
#define reps(i,a,n) for(int i=(a);i<int(n);i++)
#define rep(i,n) reps(i,0,n)
#define all(x) x.begin(),x.end()
#define pcnt(x) __builtin_popcountll(x)
#define fin(x) return cout<<x<<'\n',static_cast<void>(0)
ll myceil(ll a,ll b){return (a+b-1)/b;}
template<typename T,size_t n,size_t id=0>
auto vec(const int (&d)[n],const T &init=T()){
  if constexpr (id<n)return vector(d[id],vec<T,n,id+1>(d,init));
  else return init;
}
#ifdef LOCAL
#include<debug.h>
#else
#define debug(...) static_cast<void>(0)
#define debugg(...) static_cast<void>(0)
template<typename T1,typename T2>ostream &operator<<(ostream &os,const pair<T1,T2>&p){os<<p.first<<' '<<p.second;return os;}
#endif
void SOLVE();
int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout<<fixed<<setprecision(16);
  #ifdef LOCAL
  clock_t start=clock();
  #endif
  int testcase=1;
  //cin>>testcase;
  for(int i=0;i<testcase;i++){
    SOLVE();
  }
  #ifdef LOCAL
  cerr<<"time:";
  cerr<<(clock()-start)/1000;
  cerr<<"ms\n";
  #endif
}
using namespace std;
template<typename S,auto op,auto unit>
struct DynamicConnectivity{
private:
  #define get_id(d,u,v) (n2*(d)+(long long)n*u+v)
  #define get_e(d,u) (n*(d)+u)
  // #define e_upd(u) u->ech=(u->left?u->left->ech:0)|u->e|(u->right?u->right->ech:0)
  // #define v_upd(u) u->sum=u->left&&u->right?op(u->left->sum,op(u->v,u->right->sum)):(u->left?op(u->left->sum,u->v):(u->right?op(u->v,u->right->sum):u->v))
  #define e_upd(u) u->update()
  #define v_upd(u) u->update()
  struct node{
    node *left,*right,*par;
    S v,sum;
    int from,to;
    int sz;
    int8_t e,ech;
    node(){}
    node(int u,S x=unit()):left(nullptr),right(nullptr),par(nullptr),v(x),sum(x),from(u),to(u),sz(1),e(0),ech(0){}
    node(int from_,int to_,int):left(nullptr),right(nullptr),par(nullptr),v(unit()),sum(unit()),from(from_),to(to_),sz(0),e((from_<to_)<<1),ech((from_<to_)<<1){}
    inline void update(){
      sum=v;
      sz=from==to;
      ech=e;
      if(left)sum=op(left->sum,sum),sz+=left->sz,ech|=left->ech;
      if(right)sum=op(sum,right->sum),sz+=right->sz,ech|=right->ech;
    }
    void splay(){
      while(this->par){
        node *p=this->par;
        if(!this->par->par){
          if(p->left==this){
            if(this->right)this->right->par=p;
            p->left=this->right;
            this->right=p;
            this->par=nullptr;
            p->par=this;
          }
          else{
            if(this->left)this->left->par=p;
            p->right=this->left;
            this->left=p;
            this->par=nullptr;
            p->par=this;
          }
          p->update();
          this->update();
        }
        else{
          node *pp=p->par;
          if(p->left==this){
            if(pp->left==p){
              if(this->right)this->right->par=p;
              if(p->right)p->right->par=pp;
              p->left=this->right;
              this->right=p;
              pp->left=p->right;
              p->right=pp;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              p->par=this;
              pp->par=p;
            }
            else{
              if(this->left)this->left->par=pp;
              if(this->right)this->right->par=p;
              pp->right=this->left;
              p->left=this->right;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              pp->par=this;
              p->par=this;
              this->left=pp;
              this->right=p;
            }
          }
          else{
            if(pp->left==p){
              if(this->left)this->left->par=p;
              if(this->right)this->right->par=pp;
              p->right=this->left;
              pp->left=this->right;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              p->par=this;
              pp->par=this;
              this->left=p;
              this->right=pp;
            }
            else{
              if(this->left)this->left->par=p;
              if(p->left)p->left->par=pp;
              p->right=this->left;
              this->left=p;
              pp->right=p->left;
              p->left=pp;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              p->par=this;
              pp->par=p;
            }
          }
          pp->update();
          p->update();
          this->update();
        }
      }
      this->update();
    }
  };
  unordered_map<long long,node*>ptr;
  vector<unordered_set<int>>edge;
  int dep,n;
  long long n2;
  void reroot(node *nd){
    nd->splay();
    if(!nd->left)return;
    node *le=nd->left;
    le->par=nullptr;
    nd->left=nullptr;
    nd->update();
    while(le->left)le=le->left;
    le->splay();
    le->left=nd;
    nd->par=le;
    le->update();
    nd->splay();
  }
  node *merge(node *l,node *r){
    if(!l)return r;
    if(!r)return l;
    while(l->right)l=l->right;
    l->splay();
    l->right=r;
    r->par=l;
    l->update();
    return l;
  }
  bool same(int d,int u,int v){
    node *uu=ptr[get_id(d,u,u)];
    node *vv=ptr[get_id(d,v,v)];
    uu->splay();
    vv->splay();
    return uu->par;
  }
  bool link(int d,int u,int v){
    node *uu=ptr[get_id(d,u,u)];
    node *vv=ptr[get_id(d,v,v)];
    uu->splay(),vv->splay();
    if(uu->par)return false;
    node *uv=new node(u,v,0);
    node *vu=new node(v,u,0);
    ptr[get_id(d,u,v)]=uv;
    ptr[get_id(d,v,u)]=vu;
    reroot(uu),reroot(vv);
    uv->left=uu,uu->par=uv;
    uv->right=vv,vv->par=uv;
    uv->par=vu;
    vu->left=uv;
    uv->update();
    vu->update();
    return true;
  }
  bool cut(int d,int u,int v){
    if(ptr.find(get_id(d,u,v))==ptr.end())return false;
    node *uv=ptr[get_id(d,u,v)];
    node *vu=ptr[get_id(d,v,u)];
    uv->splay(),vu->splay();
    node *p=uv->par;
    bool ri=uv==vu->right;
    if(p!=vu){
      ri=p==vu->right;
      p->par=nullptr;
      uv->splay();
    }
    if(uv->left)uv->left->par=nullptr;
    if(uv->right)uv->right->par=nullptr;
    if(ri){
      if(vu->left)vu->left->par=nullptr;
      merge(vu->left,uv->right);
    }
    else{
      if(vu->right)vu->right->par=nullptr;
      merge(uv->left,vu->right);
    }
    ptr.erase(get_id(d,u,v));
    ptr.erase(get_id(d,v,u));
    delete(uv);
    delete(vu);
    return true;
  }
  bool replace(int d,int u,int v){
    for(int i=0;i<d;i++)cut(i,u,v);
    bool res=false;
    for(int i=d;i>=0;i--){
      node *ndu=ptr[get_id(i,u,u)];
      node *ndv=ptr[get_id(i,v,v)];
      ndu->splay();
      ndv->splay();
      if(ndu->sz>ndv->sz)swap(u,v),swap(ndu,ndv);
      while(ndu->ech&2){
        while(((~ndu->e)&2)){
          if(ndu->left&&(ndu->left->ech&2))ndu=ndu->left;
          else ndu=ndu->right;
        }
        assert(ndu->ech&2);
        ndu->splay();
        ndu->e&=1;
        e_upd(ndu);
        link(i+1,ndu->from,ndu->to);
      }
      while(ndu->ech&1){
        while((~ndu->e)&1){
          if(ndu->left&&(ndu->left->ech&1))ndu=ndu->left;
          else ndu=ndu->right;
        }
        ndu->splay();
        int s=ndu->from;
        int is=get_e(i,s);
        for(auto itr=edge[is].begin();itr!=edge[is].end();){
          int t=*itr;
          itr=edge[is].erase(itr);
          edge[get_e(i,t)].erase(s);
          if(edge[get_e(i,t)].empty()){
            node *tt=ptr[get_id(i,t,t)];
            tt->splay();
            tt->e&=2;
            e_upd(tt);
          }
          if(edge[get_e(i,s)].empty()){
            node *ss=ptr[get_id(i,s,s)];
            ss->splay();
            ss->e&=2;
            e_upd(ss);
          }
          if(same(i,s,t)){
            if(edge[get_e(i+1,s)].empty()){
              node *nd=ptr[get_id(i+1,s,s)];
              nd->splay();
              nd->ech|=nd->e|=1;
            }
            if(edge[get_e(i+1,t)].empty()){
              node *nd=ptr[get_id(i+1,t,t)];
              nd->splay();
              nd->ech|=nd->e|=1;
            }
            edge[get_e(i+1,s)].insert(t);
            edge[get_e(i+1,t)].insert(s);
          }
          else{
            for(int j=0;j<=i;j++)link(j,s,t);
            res=true;
            break;
          }
        }
        if(edge[get_e(i,s)].empty()){
          node *ss=ptr[get_id(i,s,s)];
          ss->splay();
          ss->e&=2;
          e_upd(ss);
        }
        if(res)return true;
      }
    }
    return false;
  }
public:
  DynamicConnectivity(int n_):n(n_),dep(1),edge(n_){
    n2=(long long)n*n;
    for(int i=0;i<n;i++){
      node *nd=new node(i);
      ptr[get_id(0,i,i)]=nd;
    }
  }
  bool link(int u,int v){
    if(u==v)return false;
    if(link(0,u,v))return true;
    if(edge[u].empty()){
      node *nd=ptr[get_id(0,u,u)];
      nd->splay();
      nd->ech|=nd->e|=1;
    }
    if(edge[v].empty()){
      node *nd=ptr[get_id(0,v,v)];
      nd->splay();
      nd->ech|=nd->e|=1;
    }
    edge[u].insert(v);
    edge[v].insert(u);
    return true;
  }
  bool cut(int u,int v){
    if(u==v)return false;
    for(int i=0;i<dep;i++){
      edge[get_e(i,u)].erase(v);
      edge[get_e(i,v)].erase(u);
      if(edge[get_e(i,u)].empty()){
        node *nd=ptr[get_id(i,u,u)];
        nd->splay();
        nd->e&=2;
        e_upd(nd);
      }
      if(edge[get_e(i,v)].empty()){
        node *nd=ptr[get_id(i,v,v)];
        nd->splay();
        nd->e&=2;
        e_upd(nd);
      }
    }
    for(int i=dep-1;i>=0;i--){
      if(cut(i,u,v)){
        if(i+1==dep){
          edge.resize(edge.size()+n);
          for(int j=0;j<n;j++){
            ptr[get_id(dep,j,j)]=new node(j);
          }
          dep++;
        }
        return !replace(i,u,v);
      }
    }
    return true;
  }
  bool same(int u,int v){
    node *uu=ptr[get_id(0,u,u)];
    node *vv=ptr[get_id(0,v,v)];
    uu->splay();
    vv->splay();
    return uu->par;
  }
  void set(int u,const S&x){
    node *nd=ptr[get_id(0,u,u)];
    nd->splay();
    nd->v=x;
    v_upd(nd);
  }
  S get(int u){return ptr[get_id(0,u,u)]->v;}
  S sum(int u){
    node *nd=ptr[get_id(0,u,u)];
    nd->splay();
    return nd->sum;
  }
  int size(int u){
    node *nd=ptr[get_id(0,u,u)];
    nd->splay();
    return nd->sz;
  }
  void print(){
    vector<vector<pair<int,int>>>res(dep);
    for(auto [i,p]:ptr){
      int d=i/n2;
      int frm=(i%n2)/n;
      int to=(i%n2)%n;
      res[d].push_back({frm,to});
    }
    for(int i=0;i<dep;i++)sort(res[i].begin(),res[i].end()),debug(i,res[i]);
  }
  void baka(){
    for(int i=0;i<dep*n;i++){
      for(auto p:ptr)p.second->update();
    }
  }
  void ASSERT(){
    for(int i=0;i<edge.size();i++){
      for(auto j:edge[i]){
        if(i==j){
          cerr<<i<<endl;
          exit(1);
        }
      }
    }
    for(auto [i,p]:ptr){
      assert(p->left!=p);
      assert(p->right!=p);
      assert(p->par!=p);
    }
  }
  #undef get_id
  #undef get_e
  #undef e_upd
  #undef v_upd
};
int op(int x,int y){return x+y;}
int e(){return 0;}
void SOLVE(){
  int n,q;
  cin>>n>>q;
  DynamicConnectivity<int,op,e>dycon(n);
  while(q--){
    int t,u,v;
    cin>>t>>u>>v;
    if(t==1)dycon.link(u,v);
    else if(t==2)dycon.cut(u,v);
    else cout<<(dycon.same(u,v)?"YES\n":"NO\n");
  }
}
accuracy: 1
time_ms: 290
memory_kb: 82852
final_rank: 15

code_snippet: #include<bits/stdc++.h>
using namespace std;
template<typename T>
class dynamic_connectivity{
	class euler_tour_tree{
		public:
		struct node;
		using np=node*;
		using lint=long long;
		struct node{
			np ch[2]={nullptr,nullptr};
			np p=nullptr;
			int l,r,sz;
			T val=et,sum=et;
			bool exact;
			bool child_exact;
			bool edge_connected=0;
			bool child_edge_connected=0;
			node(){}
			node(int l,int r):l(l),r(r),sz(l==r),exact(l<r),child_exact(l<r){}
			bool is_root() {
				return !p;
			}
		};
		vector<unordered_map<int,np>>ptr;
		np get_node(int l,int r){
			if(ptr[l].find(r)==ptr[l].end())ptr[l][r]=new node(l,r);
			return ptr[l][r];
		}
		np root(np t){
			if(!t)return t;
			while(t->p)t=t->p;
			return t;
		}
		bool same(np s,np t){
			if(s)splay(s);
			if(t)splay(t);
			return root(s)==root(t);
		}
		np reroot(np t){
			auto s=split(t);
			return merge(s.second,s.first);
		}
		pair<np,np> split(np s){
			splay(s);
			np t=s->ch[0];
			if(t)t->p=nullptr;
			s->ch[0]=nullptr;
			return {t,update(s)};
		}
		pair<np,np> split2(np s){
			splay(s);
			np t=s->ch[0];
			np u=s->ch[1];
			if(t)t->p=nullptr;
			s->ch[0]=nullptr;
			if(u)u->p=nullptr;
			s->ch[1]=nullptr;
			return {t,u};
		}
		tuple<np,np,np> split(np s,np t){
			auto u=split2(s);
			if(same(u.first,t)){
				auto r=split2(t);
				return make_tuple(r.first,r.second,u.second);
			}else{
				auto r=split2(t);
				return make_tuple(u.first,r.first,r.second);
			}
		}
		template<typename First, typename... Rest>
		np merge(First s,Rest... t){
			return merge(s,merge(t...));
		}
		np merge(np s,np t){
			if(!s)return t;
			if(!t)return s;
			while(s->ch[1])s=s->ch[1];
			splay(s);
			s->ch[1]=t;
			if(t)t->p=s;
			return update(s);
		}
		int size(np t){return t?t->sz:0;}
		np update(np t){
			t->sum=et;
			if(t->ch[0])t->sum=fn(t->sum,t->ch[0]->sum);
			if(t->l==t->r)t->sum=fn(t->sum,t->val);
			if(t->ch[1])t->sum=fn(t->sum,t->ch[1]->sum);
			t->sz=size(t->ch[0])+(t->l==t->r)+size(t->ch[1]);
			t->child_edge_connected=(t->ch[0]?t->ch[0]->child_edge_connected:0)|(t->edge_connected)|(t->ch[1]?t->ch[1]->child_edge_connected:0);
			t->child_exact=(t->ch[0]?t->ch[0]->child_exact:0)|(t->exact)|(t->ch[1]?t->ch[1]->child_exact:0);
			return t;
		}
		void push(np t){
			//遅延評価予定
		}
		void rot(np t,bool b){
			np x=t->p,y=x->p;
			if((x->ch[1-b]=t->ch[b]))t->ch[b]->p=x;
			t->ch[b]=x,x->p=t;
			update(x);update(t);
			if((t->p=y)){
				if(y->ch[0]==x)y->ch[0]=t;
				if(y->ch[1]==x)y->ch[1]=t;
				update(y);
			}
		}
		void splay(np t){
			push(t);
			while(!t->is_root()){
				np q=t->p;
				if(q->is_root()){
					push(q), push(t);
					rot(t,q->ch[0]==t);
				}else{
					np r=q->p;
					push(r), push(q), push(t);
					bool b=r->ch[0]==q;
					if(q->ch[1-b]==t)rot(q,b),rot(t,b);
					else rot(t,1-b),rot(t,b);
				}
			}
		}
		void debug(np t){
			if(!t)return;
			debug(t->ch[0]);
			cerr<<t->l<<"-"<<t->r<<" ";
			debug(t->ch[1]);
		}
		public:
		euler_tour_tree(){}
		euler_tour_tree(int sz){
			ptr.resize(sz);
			for(int i=0;i<sz;i++)ptr[i][i]=new node(i,i);
		}
		int size(int s){
			np t=get_node(s,s);
			splay(t);
			return t->sz;
		}
		bool same(int s,int t){
            return same(get_node(s,s),get_node(t,t));
        }
		void set_size(int sz){
			ptr.resize(sz);
			for(int i=0;i<sz;i++)ptr[i][i]=new node(i,i);
		}
		void update(int s,T x){
			np t=get_node(s,s);
			splay(t);
			t->val=fn(t->val,x);
			update(t);
		}
    template<typename F>
		void edge_update(int s,F g){
			np t=get_node(s,s);
			splay(t);
			function<void(np)>dfs=[&](np t){
				assert(t);
                if(t->l<t->r&&t->exact){
					splay(t);
					t->exact=0;
					update(t);
					g(t->l,t->r);
					return;
				}
				if(t->ch[0]&&t->ch[0]->child_exact)dfs(t->ch[0]);
				else dfs(t->ch[1]);
			};
			while(t&&t->child_exact){
				dfs(t);
				splay(t);
			}
		}
    template<typename F>
		bool try_reconnect(int s,F f){
			np t=get_node(s,s);
			splay(t);
			function<bool(np)>dfs=[&](np t)->bool{
				assert(t);
				if(t->edge_connected){
					splay(t);
					return f(t->l);
				}
				if(t->ch[0]&&t->ch[0]->child_edge_connected)return dfs(t->ch[0]);
				else return dfs(t->ch[1]);
			};
			while(t->child_edge_connected){
				if(dfs(t))return 1;
				splay(t);
			}
			return 0;
		}
		void edge_connected_update(int s,bool b){
			np t=get_node(s,s);
			splay(t);
			t->edge_connected=b;
			update(t);
		}
		bool link(int l,int r){
			if(same(l,r))return 0;
			merge(reroot(get_node(l,l)),get_node(l,r),reroot(get_node(r,r)),get_node(r,l));
			return 1;
		}
		bool cut(int l,int r){
			if(ptr[l].find(r)==ptr[l].end())return 0;
			np s,t,u;
			tie(s,t,u)=split(get_node(l,r),get_node(r,l));
			merge(s,u);
			np p=ptr[l][r];
			np q=ptr[r][l];
			ptr[l].erase(r);
			ptr[r].erase(l);
			delete p;delete q;
			return 1;
		}
		T get_sum(int p,int v){
			cut(p,v);
			np t=get_node(v,v);
			splay(t);
			T res=t->sum;
			link(p,v);
			return res;
		}
		T get_sum(int s){
			np t=get_node(s,s);
			splay(t);
			return t->sum;
		}
	};
	int dep=1;
	vector<euler_tour_tree> ett;
	vector<vector<unordered_set<int>>>edges;
	int sz;
	public:
	dynamic_connectivity(int sz):sz(sz){
		ett.emplace_back(sz);
		edges.emplace_back(sz);
	}
	bool link(int s,int t){
		if(s==t)return 0;
		if(ett[0].link(s,t))return 1;
		edges[0][s].insert(t);
		edges[0][t].insert(s);
		if(edges[0][s].size()==1)ett[0].edge_connected_update(s,1);
		if(edges[0][t].size()==1)ett[0].edge_connected_update(t,1);
		return 0;
	}
	bool same(int s,int t){
		return ett[0].same(s,t);
	}
	int size(int s){
		return ett[0].size(s);
	}
	vector<int>get_vertex(int s){
		return ett[0].vertex_list(s);
	}
	void update(int s,T x){
		ett[0].update(s,x);
	}
	T get_sum(int s){
		return ett[0].get_sum(s);
	}
	bool cut(int s,int t){
		if(s==t)return 0;
		for(int i=0;i<dep;i++){
			edges[i][s].erase(t);
			edges[i][t].erase(s);
			if(edges[i][s].size()==0)ett[i].edge_connected_update(s,0);
			if(edges[i][t].size()==0)ett[i].edge_connected_update(t,0);
		}
		for(int i=dep-1;i>=0;i--){
			if(ett[i].cut(s,t)){
				if(dep-1==i){
					dep++;
					ett.emplace_back(sz);
					edges.emplace_back(sz);
				}
				return !try_reconnect(s,t,i);
			}
		}
		return 0;
	}
	bool try_reconnect(int s,int t,int k){
		for(int i=0;i<k;i++){
			ett[i].cut(s,t);
		}
		for(int i=k;i>=0;i--){
			if(ett[i].size(s)>ett[i].size(t))swap(s,t);
			auto g=[&](int s,int t){ett[i+1].link(s,t);};
			ett[i].edge_update(s,g);
			auto f=[&](int x)->bool{
				for(auto itr=edges[i][x].begin();itr!=edges[i][x].end();){
					auto y=*itr;
					itr=edges[i][x].erase(itr);
					edges[i][y].erase(x);
					if(edges[i][x].size()==0)ett[i].edge_connected_update(x,0);
					if(edges[i][y].size()==0)ett[i].edge_connected_update(y,0);
					if(ett[i].same(x,y)){
						edges[i+1][x].insert(y);
						edges[i+1][y].insert(x);
						if(edges[i+1][x].size()==1)ett[i+1].edge_connected_update(x,1);
						if(edges[i+1][y].size()==1)ett[i+1].edge_connected_update(y,1);
					}else{
						for(int j=0;j<=i;j++){
							ett[j].link(x,y);
						}
						return 1;
					}
				}
				return 0;
			};
			if(ett[i].try_reconnect(s,f))return 1;
		}
		return 0;
	}
	constexpr static T et=T();
	constexpr static T fn(T s,T t){
		return s+t;
	}
};
int main(){
  int n,q;
  cin>>n>>q;
  dynamic_connectivity<int>dycon(n);
  while(q--){
    int t,u,v;
    cin>>t>>u>>v;
    if(t==1)dycon.link(u,v);
    else if(t==2)dycon.cut(u,v);
    else cout<<(dycon.same(u,v)?"YES\n":"NO\n");
  }
}
accuracy: 1
time_ms: 330
memory_kb: 110188
final_rank: 16

code_snippet: #include <bits/stdc++.h>
using namespace std;
#if __has_include(<atcoder/all>)
#include <atcoder/all>
#endif
using ll=long long;
using ull=unsigned long long;
using P=pair<ll,ll>;
template<typename T>using minque=priority_queue<T,vector<T>,greater<T>>;
template<typename T>bool chmax(T &a,const T &b){return (a<b?(a=b,true):false);}
template<typename T>bool chmin(T &a,const T &b){return (a>b?(a=b,true):false);}
template<typename T1,typename T2>istream &operator>>(istream &is,pair<T1,T2>&p){is>>p.first>>p.second;return is;}
template<typename T>istream &operator>>(istream &is,vector<T> &a){for(auto &i:a)is>>i;return is;}
template<typename T1,typename T2>void operator++(pair<T1,T2>&a,int n){a.first++,a.second++;}
template<typename T1,typename T2>void operator--(pair<T1,T2>&a,int n){a.first--,a.second--;}
template<typename T>void operator++(vector<T>&a,int n){for(auto &i:a)i++;}
template<typename T>void operator--(vector<T>&a,int n){for(auto &i:a)i--;}
#define reps(i,a,n) for(int i=(a);i<int(n);i++)
#define rep(i,n) reps(i,0,n)
#define all(x) x.begin(),x.end()
#define pcnt(x) __builtin_popcountll(x)
#define fin(x) return cout<<x<<'\n',static_cast<void>(0)
ll myceil(ll a,ll b){return (a+b-1)/b;}
template<typename T,size_t n,size_t id=0>
auto vec(const int (&d)[n],const T &init=T()){
  if constexpr (id<n)return vector(d[id],vec<T,n,id+1>(d,init));
  else return init;
}
#ifdef LOCAL
#include<debug.h>
#else
#define debug(...) static_cast<void>(0)
#define debugg(...) static_cast<void>(0)
template<typename T1,typename T2>ostream &operator<<(ostream &os,const pair<T1,T2>&p){os<<p.first<<' '<<p.second;return os;}
#endif
void SOLVE();
int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout<<fixed<<setprecision(16);
  #ifdef LOCAL
  clock_t start=clock();
  #endif
  int testcase=1;
  //cin>>testcase;
  for(int i=0;i<testcase;i++){
    SOLVE();
  }
  #ifdef LOCAL
  cerr<<"time:";
  cerr<<(clock()-start)/1000;
  cerr<<"ms\n";
  #endif
}
using namespace std;
template<typename S,auto op,auto unit>
struct DynamicConnectivity{
private:
  #define get_id(d,u,v) (n2*(d)+(long long)n*u+v)
  #define get_e(d,u) (n*(d)+u)
  // #define e_upd(u) u->ech=(u->left?u->left->ech:0)|u->e|(u->right?u->right->ech:0)
  // #define v_upd(u) u->sum=u->left&&u->right?op(u->left->sum,op(u->v,u->right->sum)):(u->left?op(u->left->sum,u->v):(u->right?op(u->v,u->right->sum):u->v))
  #define e_upd(u) u->update()
  #define v_upd(u) u->update()
  struct node{
    node *left,*right,*par;
    S v,sum;
    int from,to;
    int sz;
    int8_t e,ech;
    node(){}
    node(int u,S x=unit()):left(nullptr),right(nullptr),par(nullptr),v(x),sum(x),from(u),to(u),sz(1),e(0),ech(0){}
    node(int from_,int to_,int):left(nullptr),right(nullptr),par(nullptr),v(unit()),sum(unit()),from(from_),to(to_),sz(0),e((from_<to_)<<1),ech((from_<to_)<<1){}
    inline void update(){
      sum=v;
      sz=from==to;
      ech=e;
      if(left)sum=op(left->sum,sum),sz+=left->sz,ech|=left->ech;
      if(right)sum=op(sum,right->sum),sz+=right->sz,ech|=right->ech;
    }
    void splay(){
      while(this->par){
        node *p=this->par;
        if(!this->par->par){
          if(p->left==this){
            if(this->right)this->right->par=p;
            p->left=this->right;
            this->right=p;
            this->par=nullptr;
            p->par=this;
          }
          else{
            if(this->left)this->left->par=p;
            p->right=this->left;
            this->left=p;
            this->par=nullptr;
            p->par=this;
          }
          p->update();
          this->update();
        }
        else{
          node *pp=p->par;
          if(p->left==this){
            if(pp->left==p){
              if(this->right)this->right->par=p;
              if(p->right)p->right->par=pp;
              p->left=this->right;
              this->right=p;
              pp->left=p->right;
              p->right=pp;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              p->par=this;
              pp->par=p;
            }
            else{
              if(this->left)this->left->par=pp;
              if(this->right)this->right->par=p;
              pp->right=this->left;
              p->left=this->right;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              pp->par=this;
              p->par=this;
              this->left=pp;
              this->right=p;
            }
          }
          else{
            if(pp->left==p){
              if(this->left)this->left->par=p;
              if(this->right)this->right->par=pp;
              p->right=this->left;
              pp->left=this->right;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              p->par=this;
              pp->par=this;
              this->left=p;
              this->right=pp;
            }
            else{
              if(this->left)this->left->par=p;
              if(p->left)p->left->par=pp;
              p->right=this->left;
              this->left=p;
              pp->right=p->left;
              p->left=pp;
              if(pp->par){
                if(pp->par->left==pp)pp->par->left=this;
                else pp->par->right=this;
              }
              this->par=pp->par;
              p->par=this;
              pp->par=p;
            }
          }
          pp->update();
          p->update();
          this->update();
        }
      }
      this->update();
    }
  };
  unordered_map<long long,node*>ptr;
  vector<unordered_set<int>>edge;
  int dep,n;
  long long n2;
  void reroot(node *nd){
    nd->splay();
    if(!nd->left)return;
    node *le=nd->left;
    le->par=nullptr;
    nd->left=nullptr;
    nd->update();
    while(le->left)le=le->left;
    le->splay();
    le->left=nd;
    nd->par=le;
    le->update();
    nd->splay();
  }
  node *merge(node *l,node *r){
    if(!l)return r;
    if(!r)return l;
    while(l->right)l=l->right;
    l->splay();
    l->right=r;
    r->par=l;
    l->update();
    return l;
  }
  bool same(int d,int u,int v){
    node *uu=ptr[get_id(d,u,u)];
    node *vv=ptr[get_id(d,v,v)];
    uu->splay();
    vv->splay();
    return uu->par;
  }
  bool link(int d,int u,int v){
    node *uu=ptr[get_id(d,u,u)];
    node *vv=ptr[get_id(d,v,v)];
    uu->splay(),vv->splay();
    if(uu->par)return false;
    node *uv=new node(u,v,0);
    node *vu=new node(v,u,0);
    ptr[get_id(d,u,v)]=uv;
    ptr[get_id(d,v,u)]=vu;
    reroot(uu),reroot(vv);
    uv->left=uu,uu->par=uv;
    uv->right=vv,vv->par=uv;
    uv->par=vu;
    vu->left=uv;
    uv->update();
    vu->update();
    return true;
  }
  bool cut(int d,int u,int v){
    if(ptr.find(get_id(d,u,v))==ptr.end())return false;
    node *uv=ptr[get_id(d,u,v)];
    node *vu=ptr[get_id(d,v,u)];
    uv->splay(),vu->splay();
    node *p=uv->par;
    bool ri=uv==vu->right;
    if(p!=vu){
      ri=p==vu->right;
      p->par=nullptr;
      uv->splay();
    }
    if(uv->left)uv->left->par=nullptr;
    if(uv->right)uv->right->par=nullptr;
    if(ri){
      if(vu->left)vu->left->par=nullptr;
      merge(vu->left,uv->right);
    }
    else{
      if(vu->right)vu->right->par=nullptr;
      merge(uv->left,vu->right);
    }
    ptr.erase(get_id(d,u,v));
    ptr.erase(get_id(d,v,u));
    delete(uv);
    delete(vu);
    return true;
  }
  bool replace(int d,int u,int v){
    for(int i=0;i<d;i++)cut(i,u,v);
    bool res=false;
    for(int i=d;i>=0;i--){
      node *ndu=ptr[get_id(i,u,u)];
      node *ndv=ptr[get_id(i,v,v)];
      ndu->splay();
      ndv->splay();
      if(ndu->sz>ndv->sz)swap(u,v),swap(ndu,ndv);
      while(ndu->ech&2){
        while(((~ndu->e)&2)){
          if(ndu->left&&(ndu->left->ech&2))ndu=ndu->left;
          else ndu=ndu->right;
        }
        ndu->splay();
        ndu->e&=1;
        e_upd(ndu);
        link(i+1,ndu->from,ndu->to);
      }
      while(ndu->ech&1){
        while((~ndu->e)&1){
          if(ndu->left&&(ndu->left->ech&1))ndu=ndu->left;
          else ndu=ndu->right;
        }
        ndu->splay();
        int s=ndu->from;
        int is=get_e(i,s);
        debug(u,v,s,edge[is]);
        for(auto itr=edge[is].begin();itr!=edge[is].end();){
          int t=*itr;
          itr=edge[is].erase(itr);
          edge[get_e(i,t)].erase(s);
          if(edge[get_e(i,t)].empty()){
            node *tt=ptr[get_id(i,t,t)];
            tt->splay();
            tt->e&=2;
            e_upd(tt);
          }
          if(edge[get_e(i,s)].empty()){
            node *ss=ptr[get_id(i,s,s)];
            ss->splay();
            ss->e&=2;
            e_upd(ss);
          }
          if(same(i,s,t)){
            if(edge[get_e(i,s)].empty()){
              node *nd=ptr[get_id(i,s,s)];
              nd->splay();
              nd->ech|=nd->e|=1;
            }
            if(edge[get_e(i,t)].empty()){
              node *nd=ptr[get_id(i,t,t)];
              nd->splay();
              nd->ech|=nd->e|=1;
            }
            edge[get_e(i+1,s)].insert(t);
            edge[get_e(i+1,t)].insert(s);
          }
          else{
            for(int j=0;j<=i;j++)link(j,s,t);
            res=true;
            break;
          }
        }
        if(edge[get_e(i,s)].empty()){
          node *ss=ptr[get_id(i,s,s)];
          ss->splay();
          ss->e&=2;
          e_upd(ss);
        }
        if(res)return true;
      }
    }
    return false;
  }
public:
  DynamicConnectivity(int n_):n(n_),dep(1),edge(n_){
    n2=(long long)n*n;
    for(int i=0;i<n;i++){
      node *nd=new node(i);
      ptr[get_id(0,i,i)]=nd;
    }
  }
  bool link(int u,int v){
    if(u==v)return false;
    if(link(0,u,v))return true;
    if(edge[u].empty()){
      node *nd=ptr[get_id(0,u,u)];
      nd->splay();
      nd->ech|=nd->e|=1;
    }
    if(edge[v].empty()){
      node *nd=ptr[get_id(0,v,v)];
      nd->splay();
      nd->ech|=nd->e|=1;
    }
    edge[u].insert(v);
    edge[v].insert(u);
    return true;
  }
  bool cut(int u,int v){
    if(u==v)return false;
    for(int i=0;i<dep;i++){
      edge[get_e(i,u)].erase(v);
      edge[get_e(i,v)].erase(u);
      if(edge[get_e(i,u)].empty()){
        node *nd=ptr[get_id(i,u,u)];
        nd->splay();
        nd->e&=2;
        e_upd(nd);
      }
      if(edge[get_e(i,v)].empty()){
        node *nd=ptr[get_id(i,v,v)];
        nd->splay();
        nd->e&=2;
        e_upd(nd);
      }
    }
    for(int i=dep-1;i>=0;i--){
      if(cut(i,u,v)){
        if(i+1==dep){
          edge.resize(edge.size()+n);
          for(int j=0;j<n;j++){
            ptr[get_id(dep,j,j)]=new node(j);
          }
          dep++;
        }
        return !replace(i,u,v);
      }
    }
    return true;
  }
  bool same(int u,int v){
    node *uu=ptr[get_id(0,u,u)];
    node *vv=ptr[get_id(0,v,v)];
    uu->splay();
    vv->splay();
    return uu->par;
  }
  void set(int u,const S&x){
    node *nd=ptr[get_id(0,u,u)];
    nd->splay();
    nd->v=x;
    v_upd(nd);
  }
  S get(int u){return ptr[get_id(0,u,u)]->v;}
  S sum(int u){
    node *nd=ptr[get_id(0,u,u)];
    nd->splay();
    return nd->sum;
  }
  int size(int u){
    node *nd=ptr[get_id(0,u,u)];
    nd->splay();
    return nd->sz;
  }
  void print(){
    vector<vector<pair<int,int>>>res(dep);
    for(auto [i,p]:ptr){
      int d=i/n2;
      int frm=(i%n2)/n;
      int to=(i%n2)%n;
      res[d].push_back({frm,to});
    }
    for(int i=0;i<dep;i++)sort(res[i].begin(),res[i].end()),debug(i,res[i]);
  }
  void baka(){
    for(int i=0;i<dep*n;i++){
      for(auto p:ptr)p.second->update();
    }
  }
  void ASSERT(){
    for(int i=0;i<edge.size();i++){
      for(auto j:edge[i]){
        if(i==j){
          cerr<<i<<endl;
          exit(1);
        }
      }
    }
    for(auto [i,p]:ptr){
      assert(p->left!=p);
      assert(p->right!=p);
      assert(p->par!=p);
    }
  }
  #undef get_id
  #undef get_e
  #undef e_upd
  #undef v_upd
};
int op(int x,int y){return x+y;}
int e(){return 0;}
void SOLVE(){
  int n,q;
  cin>>n>>q;
  DynamicConnectivity<int,op,e>dycon(n);
  while(q--){
    int t,u,v;
    cin>>t>>u>>v;
    if(t==1)dycon.link(u,v);
    else if(t==2)dycon.cut(u,v);
    else cout<<(dycon.same(u,v)?"YES\n":"NO\n");
  }
}
accuracy: 0.6052631578947368
time_ms: 30
memory_kb: 11136
final_rank: 17

code_snippet: #include <bits/stdc++.h>




namespace zawa {

using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using i128 = __int128_t;

using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

using usize = std::size_t;

} // namespace zawa


namespace zawa {

void SetFastIO() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
}

void SetPrecision(u32 dig) {
    std::cout << std::fixed << std::setprecision(dig);
}

} // namespace zawa




namespace zawa {

template <class T>
class UndoableVector {
public:
    using Iterator = typename std::vector<T>::const_iterator;

    UndoableVector() = default;

    UndoableVector(usize n) : vec_(n) {}

    UndoableVector(usize n, const T& v) : vec_(n, v) {}

    UndoableVector(const std::vector<T>& vec) : vec_{vec} {}

    UndoableVector(std::vector<T>&& vec) : vec_{std::move(vec)} {}
    
    inline usize size() const {
        return vec_.size();
    }

    Iterator begin() const {
        return vec_.begin();
    }

    Iterator end() const {
        return vec_.end();
    }

    void assign(usize i, const T& v) {
        assert(i < size());
        save(i);
        vec_[i] = v;
    }

    T operator[](usize i) const {
        assert(i < size());
        return vec_[i];
    }

    void undo() {
        assert(history_.size());
        auto [i, v]{history_.back()};
        vec_[i] = v;
        history_.pop_back();
    }

private:
    std::vector<T> vec_; 
    std::vector<std::pair<usize, T>> history_;

    void save(usize i) {
        history_.emplace_back(i, vec_[i]);
    }
};

} // namespace zawa


namespace zawa {

class UndoableDisjointSetUnion {
public:
    UndoableDisjointSetUnion() = default;

    UndoableDisjointSetUnion(usize n) : n_{n}, data_(n, -1) {}

    u32 leader(u32 v) const {
        return data_[v] < 0 ? v : leader(data_[v]);
    }

    inline usize size() const {
        return n_;
    }

    inline usize size(u32 v) const {
        assert(v < size());
        return static_cast<usize>(-data_[leader(v)]);
    }

    bool same(u32 u, u32 v) const {
        assert(u < size());
        assert(v < size());
        return leader(u) == leader(v);
    }

    bool merge(u32 u, u32 v) {
        u = leader(u);
        v = leader(v);
        if (u == v) {
            data_.assign(u, data_[u]);
            data_.assign(v, data_[v]);
            return false;
        }
        else {
            if (data_[u] > data_[v]) std::swap(u, v);
            data_.assign(u, data_[u] + data_[v]);
            data_.assign(v, u);
            return true;
        }
    }

    void undo() {
        data_.undo();
        data_.undo();
    }

    std::vector<std::vector<u32>> enumerate() const {
        std::vector<std::vector<u32>> res(n_);
        for (u32 v{} ; v < n_ ; v++) {
            res[leader(v)].push_back(v);
        }
        res.erase(std::remove_if(res.begin(), res.end(),
                    [](const auto& arr) -> bool { return arr.empty(); }), res.end());
        return res;
    }

private:
    usize n_{};
    UndoableVector<i32> data_{};
};

} // namespace zawa
using namespace zawa;

int main() {
    SetFastIO();

    int N, K;
    std::cin >> N >> K;
    std::vector<int> T(K), U(K), V(K);
    for (int i{} ; i < K ; i++) {
        std::cin >> T[i] >> U[i] >> V[i];
    }
    std::vector<std::pair<int, int>> edges;
    for (int i{} ; i < K ; i++) {
        if (T[i] == 3) continue;
        edges.push_back(std::make_pair(U[i], V[i]));
    }
    std::sort(edges.begin(), edges.end());
    edges.erase(std::unique(edges.begin(), edges.end()), edges.end());
    std::vector<int> last(edges.size(), -1);
    std::vector<std::vector<int>> span(2 * K);
    for (int i{} ; i < K ; i++) {
        if (T[i] == 3) continue;
        int id{(int)std::distance(edges.begin(), std::lower_bound(edges.begin(), edges.end(), std::make_pair(U[i], V[i])))};
        if (T[i] == 1) {
            assert(last[id] == -1);
            last[id] = i;
        }
        else if (T[i] == 2) {
            assert(last[id] != -1);
            int L{last[id]}, R{i};
            last[id] = -1;
            for (L += K, R += K ; L < R ; L >>= 1, R >>= 1) {
                if (L & 1) {
                    span[L].push_back(id);
                    L++;
                }
                if (R & 1) {
                    R--;
                    span[R].push_back(id);
                }
            }
        }
    }
    for (int i{} ; i < (int)last.size() ; i++) {
        if (last[i] == -1) continue;
        int L{last[i]}, R{K};
        for (L += K, R += K ; L < R ; L >>= 1, R >>= 1) {
            if (L & 1) {
                span[L].push_back(i);
                L++;
            }
            if (R & 1) {
                R--;
                span[R].push_back(i);
            }
        }
    }
    std::vector<bool> ans(K);
    UndoableDisjointSetUnion dsu(N);
    auto dfs{[&](auto dfs, int v) -> void {
        // std::cout << v << std::endl;
        for (auto i : span[v]) {
            auto [u, v]{edges[i]};
            // std::cout << '(' << u << ',' << v << ')' << ' ';
            dsu.merge(u, v);
        }
        // std::cout << std::endl;
        if (v < K) {
            dfs(dfs, 2 * v);
            dfs(dfs, 2 * v + 1);
        }
        else {
            if (T[v - K] == 3) {
                ans[v - K] = dsu.same(U[v - K], V[v - K]);
            }
        }
        for (int i{} ; i < (int)span[v].size() ; i++) {
            dsu.undo();
        }
    }};
    dfs(dfs, 1);
    for (int i{} ; i < K ; i++) {
        if (T[i] == 3) {
            std::cout << (ans[i] ? "YES" : "NO") << '\n';
        }
    }
}
accuracy: 1
time_ms: 20
memory_kb: 9920
final_rank: 1

code_snippet: /**
*    author:  shu8Cream
*    created: 2024/04/09 20:51:55
**/

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct UndoableUnionFind{
    int n;
    vector<int> par;
    vector<pair<int,int>> history;
    int group_cnt;
    UndoableUnionFind(){}
    UndoableUnionFind(int n=0): n(n), par(n, -1), group_cnt(n) {}
    int find(int x){
        if(par[x] < 0) return x;
        return find(par[x]);
    }
    bool merge(int x, int y){
        x = find(x); y = find(y);
        history.emplace_back(x,par[x]);
        history.emplace_back(y,par[y]);
        if(x == y) return false;
        if(par[x] > par[y]) swap(x,y); //マージテク
        par[x] += par[y];
        par[y] = x;
        group_cnt--;
        return true;
    }
    bool same(int x, int y) {return find(x) == find(y);}
    int size(int x) {return -par[find(x)];}

    void undo(){
        int u = history.back().first;
        par[history.back().first] = history.back().second;
        history.pop_back();
        int v = history.back().first;
        par[history.back().first] = history.back().second;
        history.pop_back();
        if(u!=v) group_cnt++;
    }

    void snapshot() {
        while (!history.empty()) history.pop_back();
    }

    void rollback() {
        while (!history.empty()) undo();
    }
};

template<typename Q>
struct OfflineDynamicConnectivity{
    struct Edge_info{
        int from, to, start, end;
        Edge_info(int from, int to, int start, int end):
            from(from),to(to),start(start),end(end)
        {}
    };
    int n,q,size,time;
    vector<Edge_info> infos;
    map<pair<int,int>,int> addtime;
    vector<vector<pair<int,int> > > seg;
    vector<Q> query_types;
    UndoableUnionFind uf;

    OfflineDynamicConnectivity(int n, int q): n(n),q(q),uf(n),time(0),query_types(q){
        for(size=1; size<q; size<<=1);
        seg.resize(size<<1);
    }
    void link(Q type, int u, int v){
        if(u>v) swap(u,v);
        addtime[{u,v}] = time;
        query_types[time++] = type;
    }
    void cut(Q type, int u, int v){
        if(u>v) swap(u,v);
        auto e = make_pair(u,v);
        infos.emplace_back(u,v,addtime[e],time);
        query_types[time++] = type;
        addtime.erase(e);
    }
    void query(Q type){
        query_types[time++] = type;
    }
    template<typename T>
    void dfs(const T &func, int node=1){
        for(auto j : seg[node]) uf.merge(j.first, j.second);
        if(node<size){
            dfs(func, node << 1);
            dfs(func, node << 1 | 1);
        }else{
            func(node-size, uf);
        }
        for(auto j : seg[node]) uf.undo();
    }
    template<typename T>
    void run(const T &func, int node=1){
        for(auto &[e,time]:addtime) infos.emplace_back(e.first,e.second,time,size);
        for(auto i:infos){
            int l = i.start+size;
            int r = i.end+size;
            for(; l<r; l>>=1,r>>=1){
                if(r&1) seg[--r].emplace_back(i.from,i.to);
                if(l&1) seg[l++].emplace_back(i.from,i.to);
            }
        }
        dfs(func,1);
    }
};

void AOJ2235(){
    int n,q; cin >> n >> q;
    OfflineDynamicConnectivity<int> dc(n,q);
    vector<int> u(q),v(q);
    for(int t=0; t<q; t++){
        int op;
        cin >> op >> u[t] >> v[t];
        if(op==1){
            dc.link(op,u[t],v[t]);
        }else if(op==2){
            dc.cut(op,u[t],v[t]);
        }else{
            dc.query(op);
        }
    }
    dc.run([&](int t, UndoableUnionFind &uf){
        if(t<q && dc.query_types[t]==3){
            cout << (uf.same(u[t],v[t]) ? "YES" : "NO") << endl;
        }
    });
}

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(15);
    AOJ2235();
}
accuracy: 1
time_ms: 20
memory_kb: 13760
final_rank: 2

code_snippet: #include<bits/stdc++.h>
using namespace std;
using ll = long long;
using vvl = vector<vector<ll>>;
using vl = vector<ll>;
#define rep(i, s, f) for(long long i = s; i <= f; i++)
#define ENDL '\n'


ll op(ll l, ll r) {
	return l + r;
}


struct dynamic_connectivity {
    struct euler_tour_tree {
    	struct node_t;
    	using np = node_t*;
    	struct node_t {
    		node_t *lch, *rch, *par;
			int s, t;
    		int sz;
			bool exact;//このノード(辺) のレベル == ettのレベル && s < t   edge_levelup
			bool child_exact;
			bool have_auxedge = false;
			bool child_have_auxedge = false;
    
    		//node_t() : lch(nullptr), rch(nullptr), par(nullptr), sz(0) {}
			node_t(int _s, int _t) : s(_s), t(_t), lch(nullptr), rch(nullptr), par(nullptr), sz(s==t), exact(s<t), child_exact(s<t) {

			}
    	    bool is_root() {
    	    	return !par;
    	    }
    
    		void take_copy(np x) {//xの集約値、作用素を自身にコピー. xはnullptrでは無い。
				sz = x -> sz;
				child_have_auxedge = x -> child_have_auxedge;
				child_exact = x -> child_exact;
    		}
    	};
    
    	
    
    
    	int sz;
    	vector<unordered_map<int, np>> ptr;

    	void push(np t) {}
    
    	void update(np t) {
    	    t -> sz = (t->s == t->t);
			if(t -> lch) t -> sz += t -> lch -> sz;
			if(t -> rch) t -> sz += t -> rch -> sz;
			t -> child_exact = (t -> lch ? t -> lch -> child_exact : 0) | (t -> exact) | (t -> rch ? t -> rch -> child_exact : 0);
			t -> child_have_auxedge = (t -> lch ? t -> lch -> child_have_auxedge : 0) | (t -> have_auxedge) | (t -> rch ? t -> rch -> child_have_auxedge : 0);
    	}
        
		void update_only_child_have_auxedge(np t) {
			t -> child_have_auxedge = (t -> lch ? t -> lch -> child_have_auxedge : 0) | (t -> have_auxedge) | (t -> rch ? t -> rch -> child_have_auxedge : 0);
		}
		void update_only_child_exact(np t) {
			t -> child_exact = (t -> lch ? t -> lch -> child_exact : 0) | (t -> exact) | (t -> rch ? t -> rch -> child_exact : 0);
		}
    
    	void rotR(np t) {
    		np x = t -> par, y = x -> par;
    		push(t);
    		if((x -> lch = t -> rch)) t -> rch -> par = x;
    		t -> rch = x, x -> par = t;
    		if((t -> par = y)) {
    			if(y -> lch == x) y -> lch = t;
    			else y -> rch = t;
    		}
    		t -> take_copy(x);
    		push(x -> lch); push(x -> rch);
    		update(x); 
    	}
    
    	void rotL(np t) {
    		np x = t -> par, y = x -> par;
    		push(t);
    		if((x -> rch = t -> lch)) t -> lch -> par = x;
    		t -> lch = x, x -> par = t;
    		if((t -> par = y)) {
    			if(y -> lch == x) y -> lch = t;
    			else y -> rch = t;
    		}
    	    t -> take_copy(x);
    		push(x -> lch); push(x -> rch);
    		update(x); 
    	}
    
    	void splay(np t) {
    		while(!t -> is_root()) {
    			np x = t -> par;
    			if(x -> is_root()) {
    				if(x -> lch == t) rotR(t);
    				else rotL(t);
    			} else {
    				np y = x -> par;
    				if(y -> lch == x) {
    					if(x -> lch == t) rotR(x), rotR(t);
    					else rotL(t), rotR(t);
    				}
    				else {
    					if(x -> rch == t) rotL(x), rotL(t);
    					else rotR(t), rotL(t);
    				}
    			}
    		}
    		push(t);//tのlazyを恒等作用にしておく
    	}
    
    	np merge(np l, np r) {
    		if(!l) return r;
    		if(!r) return l;
    		while(l -> rch) l = l -> rch;
    		splay(l);
    		if((l -> rch = r)) {
    			push(r);
    			r -> par = l;
    		}
    		update(l);
    		return l;
    	}
    
    
    	pair<np, np> split(np t) {//  t) [t, 
    	    splay(t);
    		np l = t -> lch;
    		if(l) l -> par = nullptr;
    		t -> lch = nullptr;
    		update(t);
    		return make_pair(l, t);
    	}

    	pair<np, np> split2(np t) { //move t to top and detach t
    		splay(t);
    		np l = t -> lch;
    		np r = t -> rch;
    		if(l) l -> par = nullptr;
    		t -> lch = nullptr;
    		if(r) r -> par = nullptr;
    		t -> rch = nullptr;
    		update(t);
    		return make_pair(l, r);
    	}

    	tuple<np, np, np> split(np u, np t) {// u) (u  t) (t    /    t) (t  u) (u
    	    auto v = split2(u);
    		if(same(v.first, t)) {
    			auto r = split2(t);
    			return make_tuple(r.first, r.second, v.second);
    		} else {
    			auto r = split2(t);
    			return make_tuple(v.first, r.first, r.second);
    		}
    	}
    
    	np get_node(int s, int t) {
    		if(ptr[s].find(t) == ptr[s].end())ptr[s][t] = new node_t(s, t);
    		return ptr[s][t];
    	}
    
    	np root(np t) {
    		if(!t) return t;
    		while(t -> par) t = t -> par;
    		return t;
    	}

    	np reroot(np t) {
    		auto[l, r] = split(t);
    		return merge(r, l);
    	}
		int size(np u) { // NEW!
			splay(u);
			return u -> sz;
		}
    
    	bool same(np u, np v) {
    		if(u) splay(u);
    		if(v) splay(v);
    		return root(u) == root(v);
    	}
		euler_tour_tree(){}
    	
    	euler_tour_tree(int _sz) : sz(_sz) {
    	    ptr.resize(sz);
    	}

    	bool same(int u, int v) {
    		return same(get_node(u, u), get_node(v, v));
    	}

    	bool link(int u, int v) {
    		if(same(u, v)) return false;
    		merge(merge(merge(reroot(get_node(u, u)), get_node(u, v)), reroot(get_node(v, v))), get_node(v, u));
    		return true;
    	}

    	bool cut(int u, int v) {
    		if(ptr[u].find(v) == ptr[u].end()) return false;
    		auto [i, j, k] = split(get_node(u, v), get_node(v, u));
    		merge(i, k);
    		np p = ptr[u][v];
    		np q = ptr[v][u];
    		ptr[u].erase(v);
    		ptr[v].erase(u);
    		delete p; delete q;
    		return true;
    	}
		int size(int u) {
			return size(get_node(u, u));
		}
		void have_auxedge_update(int t, bool b) {
			np v = get_node(t, t);
			splay(v);
			v -> have_auxedge = b;
			update_only_child_have_auxedge(v);
		}

		vector<int> vertex_list(int t) {
			np v = get_node(t, t);
			splay(v);
			vector<int> res;

			auto dfs = [&](auto f, np now) -> void {
				if(now -> lch) f(f, now -> lch);
				if(now -> s == now -> t) res.push_back(now -> s);
				if(now -> rch) f(f, now -> rch);
				return;
			};

			dfs(dfs, v);
			return res;
		}

    };
    int cc;
    int sz;
	int dep;
    vector<euler_tour_tree> ett;
	vector<vector<unordered_set<int>>> aux_edges;
    using np = typename euler_tour_tree::node_t*;

	private:
	 

	    void edge_level_increment(int t, int k) {
			np v = ett[k].get_node(t, t);
			ett[k].splay(v);
			auto dfs = [&](auto f, np now) -> void {
				if(now->exact) {
					ett[k].splay(now);
					now -> exact = 0;
					ett[k].update_only_child_exact(now);
					ett[k+1].link(now -> s, now -> t);
					return;
				}
				if(now -> lch && now -> lch -> child_exact) f(f, now -> lch);
				else f(f, now -> rch);
				return;
			};

			while(v && v -> child_exact) {
				dfs(dfs, v);
				ett[k].splay(v);
			}
		}
	    
		bool try_reconnect(int u, int v, int k) {
			for(int i=0; i<k;i++) {
				ett[i].cut(u, v);
			}

			for(int i=k; i >=0; i--) {
				if(ett[i].size(u) > ett[i].size(v)) swap(u, v);
				edge_level_increment(u, i);

				np t = ett[i].get_node(u, u);
				ett[i].splay(t);
				auto dfs = [&](auto f, np now) -> bool {
					if(now -> have_auxedge) {
						ett[i].splay(now);
						for(auto itr = aux_edges[i][now->s].begin(); itr != aux_edges[i][now->s].end(); ) {
							auto y = *itr;
							itr = aux_edges[i][now->s].erase(itr);
							aux_edges[i][y].erase(now->s);
							if(aux_edges[i][now->s].size()==0)ett[i].have_auxedge_update(now->s, 0);
							if(aux_edges[i][y].size()==0)ett[i].have_auxedge_update(y, 0);
							if(ett[i].same(now->s, y)) {//辺の先が自分の連結成分
							    aux_edges[i+1][now->s].insert(y);
								aux_edges[i+1][y].insert(now->s);
								if(aux_edges[i+1][now->s].size()==1)ett[i+1].have_auxedge_update(now->s, 1);
								if(aux_edges[i+1][y].size()==1)ett[i+1].have_auxedge_update(y, 1);
							}
							else {
								for(int j = 0; j <= i; j++) {
									ett[j].link(now->s, y);
								}
								return 1;
							}
						}
						return 0;
					}

					if(now -> lch && now -> lch -> child_have_auxedge) return f(f, now -> lch);
					else return f(f, now -> rch);
				};

				while(t -> child_have_auxedge) {
					if(dfs(dfs, t)) return 1;
					ett[i].splay(t);
				}
			}
			return 0;
		}

	public:

	   


	    dynamic_connectivity(int _sz) : sz(_sz) {
			cc = sz;
			dep = 1;
			ett.emplace_back(sz);
			aux_edges.emplace_back(sz);
		}


		bool same(int u, int v) {
			return ett[0].same(u, v);
		}

		bool link(int u, int v) {//連結成分が減ったか
			if(u==v)return false;
			if(ett[0].link(u, v)) {
				cc--;
				return true;
			} 
			aux_edges[0][u].insert(v);
			aux_edges[0][v].insert(u);
			if(aux_edges[0][u].size()==1)ett[0].have_auxedge_update(u, 1);
			if(aux_edges[0][v].size()==1)ett[0].have_auxedge_update(v, 1);
			return false;
		}

		bool cut(int u, int v) {//切った結果、分断されたか？
			for(int i=0; i<dep; i++) {
				aux_edges[i][u].erase(v);
				aux_edges[i][v].erase(u);
				if(aux_edges[i][u].size()==0)ett[i].have_auxedge_update(u, 0);
				if(aux_edges[i][v].size()==0)ett[i].have_auxedge_update(v, 0);
			}

			for(int i=dep-1; i >=0; i--) {
				if(ett[i].cut(u, v)) {
					if(dep-1==i) {
						dep++;
						ett.emplace_back(sz);
						aux_edges.emplace_back(sz);
					}
					bool b = try_reconnect(u, v, i);
					if(!b)cc++;
					return b;
				}
			}
			return false;
		}

		int component_count() {
			return cc;

		}

		int size(int u) {
			np v = ett[0].get_node(u, u);
			ett[0].splay(v);
			return v -> sz;
		}

		vector<int> vertex_list(int u) {
			return ett[0].vertex_list(u);
		}

		/*
		    dynamic_connectivity(sz)   O(sz)  空間O(szlogsz) (重い!)
			@brief : dynamic_connectivity 値集約なし
			link(u, v) ... uv間に辺を張る。すでにあったら何もしない。amortized O(log^2N)
			cut(u, v) ... uv間の辺を削除する。すでにあったら何もしない。amortized O(log^2N)
			same(u, v) ... uvが同じ連結成分にあるか。 O(logN)
			size(u) ... uが属する連結成分のサイズ 未verify
			component_count()...連結成分の個数 O(1)
			vertex_list(u) ... uが属する連結成分の頂点 O(logN + |SIZE|)  未verify
		*/

};

    
    
int main() {
	ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    cout << fixed << setprecision(15);

	int n, k;
	cin >> n >> k;
	dynamic_connectivity tr(n);
	while(k--) {
		int t;
		cin >> t;
		int u, v;
		cin >> u >> v;
		if(t==1) {
			tr.link(u, v);
		}else if(t==2) {
			tr.cut(u, v);
		}
		else {
			if(tr.same(u,v)) cout << "YES" << '\n';
			else cout << "NO" << '\n';
		}
	}




}
accuracy: 1
time_ms: 220
memory_kb: 77344
final_rank: 10

code_snippet: #include<bits/stdc++.h>
using namespace std;
using ll = long long;
using vvl = vector<vector<ll>>;
using vl = vector<ll>;
#define rep(i, s, f) for(long long i = s; i <= f; i++)
#define ENDL '\n'


ll op(ll l, ll r) {
	return l + r;
}


struct dynamic_connectivity {
    struct euler_tour_tree {
    	struct node_t;
    	using np = node_t*;
    	struct node_t {
    		node_t *lch, *rch, *par;
			int s, t;
    		int sz;
			bool exact;//このノード(辺) のレベル == ettのレベル && s < t   edge_levelup
			bool child_exact;
			bool have_auxedge = false;
			bool child_have_auxedge = false;
    
    		//node_t() : lch(nullptr), rch(nullptr), par(nullptr), sz(0) {}
			node_t(int _s, int _t) : s(_s), t(_t), lch(nullptr), rch(nullptr), par(nullptr), sz(s==t), exact(s<t), child_exact(s<t) {

			}
    	    bool is_root() {
    	    	return !par;
    	    }
    
    		void take_copy(np x) {//xの集約値、作用素を自身にコピー. xはnullptrでは無い。
				sz = x -> sz;
				child_have_auxedge = x -> child_have_auxedge;
				child_exact = x -> child_exact;
    		}
    	};
    
    	
    
    
    	int sz;
    	vector<unordered_map<int, np>> ptr;

    	void push(np t) {}
    
    	void update(np t) {
    	    t -> sz = (t->s == t->t);

			if(t -> lch) t -> sz += t -> lch -> sz;
			if(t -> rch) t -> sz += t -> rch -> sz;
			t -> child_exact = (t -> lch ? t -> lch -> child_exact : 0) | (t -> exact) | (t -> rch ? t -> rch -> child_exact : 0);
			t -> child_have_auxedge = (t -> lch ? t -> lch -> child_have_auxedge : 0) | (t -> have_auxedge) | (t -> rch ? t -> rch -> child_have_auxedge : 0);
    	}
        
		void update_only_child_have_auxedge(np t) {
			t -> child_have_auxedge = (t -> lch ? t -> lch -> child_have_auxedge : 0) | (t -> have_auxedge) | (t -> rch ? t -> rch -> child_have_auxedge : 0);
		}
		void update_only_child_exact(np t) {
			t -> child_exact = (t -> lch ? t -> lch -> child_exact : 0) | (t -> exact) | (t -> rch ? t -> rch -> child_exact : 0);
		}
    
    	void rotR(np t) {
    		np x = t -> par, y = x -> par;
    		//push(t);
    		if((x -> lch = t -> rch)) t -> rch -> par = x;
    		t -> rch = x, x -> par = t;
    		if((t -> par = y)) {
    			if(y -> lch == x) y -> lch = t;
    			else y -> rch = t;
    		}
    		t -> take_copy(x);
    		push(x -> lch); push(x -> rch);
    		update(x); 
    	}
    
    	void rotL(np t) {
    		np x = t -> par, y = x -> par;
    		push(t);
    		if((x -> rch = t -> lch)) t -> lch -> par = x;
    		t -> lch = x, x -> par = t;
    		if((t -> par = y)) {
    			if(y -> lch == x) y -> lch = t;
    			else y -> rch = t;
    		}
    	    t -> take_copy(x);
    		push(x -> lch); push(x -> rch);
    		update(x); 
    	}
    
    	void splay(np t) {
    		while(!t -> is_root()) {
    			np x = t -> par;
    			if(x -> is_root()) {
    				if(x -> lch == t) rotR(t);
    				else rotL(t);
    			} else {
    				np y = x -> par;
    				if(y -> lch == x) {
    					if(x -> lch == t) rotR(x), rotR(t);
    					else rotL(t), rotR(t);
    				}
    				else {
    					if(x -> rch == t) rotL(x), rotL(t);
    					else rotR(t), rotL(t);
    				}
    			}
    		}
    		push(t);//tのlazyを恒等作用にしておく
    	}
    
    	np merge(np l, np r) {
    		if(!l) return r;
    		if(!r) return l;
    		while(l -> rch) l = l -> rch;
    		splay(l);
    		if((l -> rch = r)) {
    			push(r);
    			r -> par = l;
    		}
    		update(l);
    		return l;
    	}
    
    
    	pair<np, np> split(np t) {//  t) [t, 
    	    splay(t);
    		np l = t -> lch;
    		if(l) l -> par = nullptr;
    		t -> lch = nullptr;
    		update(t);
    		return make_pair(l, t);
    	}

    	pair<np, np> split2(np t) { //move t to top and detach t
    		splay(t);
    		np l = t -> lch;
    		np r = t -> rch;
    		if(l) l -> par = nullptr;
    		t -> lch = nullptr;
    		if(r) r -> par = nullptr;
    		t -> rch = nullptr;
    		update(t);
    		return make_pair(l, r);
    	}

    	tuple<np, np, np> split(np u, np t) {// u) (u  t) (t    /    t) (t  u) (u
    	    auto v = split2(u);
    		if(same(v.first, t)) {
    			auto r = split2(t);
    			return make_tuple(r.first, r.second, v.second);
    		} else {
    			auto r = split2(t);
    			return make_tuple(v.first, r.first, r.second);
    		}
    	}
    
    	np get_node(int s, int t) {
    		if(ptr[s].find(t) == ptr[s].end())ptr[s][t] = new node_t(s, t);
    		return ptr[s][t];
    	}
    
    	np root(np t) {
    		if(!t) return t;
    		while(t -> par) t = t -> par;
    		return t;
    	}

    	np reroot(np t) {
    		auto[l, r] = split(t);
    		return merge(r, l);
    	}
		int size(np u) { // NEW!
			splay(u);
			return u -> sz;
		}
    
    	bool same(np u, np v) {
    		if(u) splay(u);
    		if(v) splay(v);
    		return root(u) == root(v);
    	}
		euler_tour_tree(){}
    	
    	euler_tour_tree(int _sz) : sz(_sz) {
    	    ptr.resize(sz);
    	}

    	bool same(int u, int v) {
    		return same(get_node(u, u), get_node(v, v));
    	}

    	bool link(int u, int v) {
    		if(same(u, v)) return false;
    		merge(merge(merge(reroot(get_node(u, u)), get_node(u, v)), reroot(get_node(v, v))), get_node(v, u));
    		return true;
    	}

    	bool cut(int u, int v) {
    		if(ptr[u].find(v) == ptr[u].end()) return false;
    		auto [i, j, k] = split(get_node(u, v), get_node(v, u));
    		merge(i, k);
    		np p = ptr[u][v];
    		np q = ptr[v][u];
    		ptr[u].erase(v);
    		ptr[v].erase(u);
    		delete p; delete q;
    		return true;
    	}
		int size(int u) {
			return size(get_node(u, u));
		}
		void have_auxedge_update(int t, bool b) {
			np v = get_node(t, t);
			splay(v);
			v -> have_auxedge = b;
			update_only_child_have_auxedge(v);
		}

    };

    int sz;
	int dep;
    vector<euler_tour_tree> ett;
	vector<vector<unordered_set<int>>> aux_edges;
    using np = typename euler_tour_tree::node_t*;

	private:
	 

	    void edge_level_increment(int t, int k) {
			np v = ett[k].get_node(t, t);
			ett[k].splay(v);
			auto dfs = [&](auto f, np now) -> void {
				if(now->exact) {
					ett[k].splay(now);
					now -> exact = 0;
					ett[k].update_only_child_exact(now);
					ett[k+1].link(now -> s, now -> t);
					return;
				}
				if(now -> lch && now -> lch -> child_exact) f(f, now -> lch);
				else f(f, now -> rch);
				return;
			};

			while(v && v -> child_exact) {
				dfs(dfs, v);
				ett[k].splay(v);
			}
		}
	    
		bool try_reconnect(int u, int v, int k) {
			for(int i=0; i<k;i++) {
				ett[i].cut(u, v);
			}

			for(int i=k; i >=0; i--) {
				if(ett[i].size(u) > ett[i].size(v)) swap(u, v);
				edge_level_increment(u, i);

				np t = ett[i].get_node(u, u);
				ett[i].splay(t);
				auto dfs = [&](auto f, np now) -> bool {
					if(now -> have_auxedge) {
						ett[i].splay(now);
						for(auto itr = aux_edges[i][now->s].begin(); itr != aux_edges[i][now->s].end(); ) {
							auto y = *itr;
							itr = aux_edges[i][now->s].erase(itr);
							aux_edges[i][y].erase(now->s);
							if(aux_edges[i][now->s].size()==0)ett[i].have_auxedge_update(now->s, 0);
							if(aux_edges[i][y].size()==0)ett[i].have_auxedge_update(y, 0);
							if(ett[i].same(now->s, y)) {//辺の先が自分の連結成分
							    aux_edges[i+1][now->s].insert(y);
								aux_edges[i+1][y].insert(now->s);
								if(aux_edges[i+1][now->s].size()==1)ett[i+1].have_auxedge_update(now->s, 1);
								if(aux_edges[i+1][y].size()==1)ett[i+1].have_auxedge_update(y, 1);
							}
							else {
								for(int j = 0; j <= i; j++) {
									ett[j].link(now->s, y);
								}
								return 1;
							}
						}
						return 0;
					}

					if(now -> lch && now -> lch -> child_have_auxedge) return f(f, now -> lch);
					else return f(f, now -> rch);
				};

				while(t -> child_have_auxedge) {
					if(dfs(dfs, t)) return 1;
					ett[i].splay(t);
				}
			}
			return 0;
		}

	public:


	    dynamic_connectivity(int _sz) : sz(_sz) {
			dep = 1;
			ett.emplace_back(sz);
			aux_edges.emplace_back(sz);
		}


		bool same(int u, int v) {
			return ett[0].same(u, v);
		}

		bool link(int u, int v) {
			if(u==v)return false;
			if(ett[0].link(u, v)) return true;
			aux_edges[0][u].insert(v);
			aux_edges[0][v].insert(u);
			if(aux_edges[0][u].size()==1)ett[0].have_auxedge_update(u, 1);
			if(aux_edges[0][v].size()==1)ett[0].have_auxedge_update(v, 1);
			return false;
		}

		bool cut(int u, int v) {//切った結果、分断されたか？
			for(int i=0; i<dep; i++) {
				aux_edges[i][u].erase(v);
				aux_edges[i][v].erase(u);
				if(aux_edges[i][u].size()==0)ett[i].have_auxedge_update(u, 0);
				if(aux_edges[i][v].size()==0)ett[i].have_auxedge_update(v, 0);
			}

			for(int i=dep-1; i >=0; i--) {
				if(ett[i].cut(u, v)) {
					if(dep-1==i) {
						dep++;
						ett.emplace_back(sz);
						aux_edges.emplace_back(sz);
					}
					return !try_reconnect(u, v, i);
				}
			}
			return false;
		}

		int size(int u) {
			np v = ett[0].get_node(u, u);
			ett[0].splay(v);
			return v -> sz;
		}


	
};
    
int main() {
	ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    cout << fixed << setprecision(15);

	int n, k;
	cin >> n >> k;
	dynamic_connectivity tr(n);
	while(k--) {
		int t;
		cin >> t;
		int u, v;
		cin >> u >> v;
		if(t==1) {
			tr.link(u, v);
		}else if(t==2) {
			tr.cut(u, v);
		}
		else {
			if(tr.same(u,v)) cout << "YES" << '\n';
			else cout << "NO" << '\n';
		}
	}




}
accuracy: 1
time_ms: 220
memory_kb: 77580
final_rank: 11

code_snippet: //#define _GLIBCXX_DEBUG
#include<bits/stdc++.h>
using namespace std;

template<typename X>
struct dynamic_connectivity {
    struct euler_tour_tree {
    	struct node_t;
    	using np = node_t*;
    	struct node_t {
    		node_t *lch, *rch, *par;
			int s, t;
    		//X key;
    		//X sum;
    		int sz;
			bool exact;//このノード(辺) のレベル == ettのレベル && s < t   edge_levelup
			bool child_exact;
			bool have_auxedge = false;
			bool child_have_auxedge = false;
    
    		//node_t() : lch(nullptr), rch(nullptr), par(nullptr), sz(0) {}
			node_t(int _s, int _t) : s(_s), t(_t), lch(nullptr), rch(nullptr), par(nullptr), sz(s==t), exact(s<t), child_exact(s<t) {}
    	    bool is_root() {
    	    	return !par;
    	    }
    
    		void take_copy(np x) {//xの集約値、作用素を自身にコピー. xはnullptrでは無い。
				//sum = x -> sum;
				sz = x -> sz;
				child_have_auxedge = x -> child_have_auxedge;
				child_exact = x -> child_exact;
    		}
    	};
    
    	
    
    
    	int sz;
    	vector<unordered_map<int, np>> ptr;

    	void push(np t) {}
    
    	void update(np t) {
    	    t -> sz = 1;
    		//t -> sum = t -> key;
    		//if(t -> lch) t -> sz += t -> lch -> sz, t -> sum = X::op(t -> lch -> sum, t -> sum);
    		//if(t -> rch) t -> sz += t -> rch -> sz, t -> sum = X::op(t -> sum, t -> rch -> sum);
			if(t -> lch) t -> sz += t -> lch -> sz;
			if(t -> rch) t -> sz += t -> rch -> sz;
			t -> child_exact = (t -> lch ? t -> lch -> child_exact : 0) | (t -> exact) | (t -> rch ? t -> rch -> child_exact : 0);
			t -> child_have_auxedge = (t -> lch ? t -> lch -> child_have_auxedge : 0) | (t -> have_auxedge) | (t -> rch ? t -> rch -> child_have_auxedge : 0);
    	}
        
		void update_only_child_have_auxedge(np t) {
			//update(t);
			t -> child_have_auxedge = (t -> lch ? t -> lch -> child_have_auxedge : 0) | (t -> have_auxedge) | (t -> rch ? t -> rch -> child_have_auxedge : 0);
		}
		void update_only_child_exact(np t) {
			//update(t);
			t -> child_exact = (t -> lch ? t -> lch -> child_exact : 0) | (t -> exact) | (t -> rch ? t -> rch -> child_exact : 0);
		}
    
    	void rotR(np t) {
    		np x = t -> par, y = x -> par;
    		//push(t);
    		if((x -> lch = t -> rch)) t -> rch -> par = x;
    		t -> rch = x, x -> par = t;
    		if((t -> par = y)) {
    			if(y -> lch == x) y -> lch = t;
    			else y -> rch = t;
    		}
    		t -> take_copy(x);
    		//push(x -> lch); push(x -> rch);
    		update(x); 
    	}
    
    	void rotL(np t) {
    		np x = t -> par, y = x -> par;
    		push(t);
    		if((x -> rch = t -> lch)) t -> lch -> par = x;
    		t -> lch = x, x -> par = t;
    		if((t -> par = y)) {
    			if(y -> lch == x) y -> lch = t;
    			else y -> rch = t;
    		}
    	    t -> take_copy(x);
    		push(x -> lch); push(x -> rch);
    		update(x); 
    	}
    
    	void splay(np t) {
    		while(!t -> is_root()) {
    			np x = t -> par;
    			if(x -> is_root()) {
    				if(x -> lch == t) rotR(t);
    				else rotL(t);
    			} else {
    				np y = x -> par;
    				if(y -> lch == x) {
    					if(x -> lch == t) rotR(x), rotR(t);
    					else rotL(t), rotR(t);
    				}
    				else {
    					if(x -> rch == t) rotL(x), rotL(t);
    					else rotR(t), rotL(t);
    				}
    			}
    		}
    		//push(t);//tのlazyを恒等作用にしておく
    	}
    
    	np merge(np l, np r) {
    		if(!l) return r;
    		if(!r) return l;
    		while(l -> rch) l = l -> rch;
    		splay(l);
    		if((l -> rch = r)) {
    			//push(r);
    			r -> par = l;
    		}
    		update(l);
    		return l;
    	}
    
    
    	pair<np, np> split(np t) {//  t) [t, 
    	    splay(t);
    		np l = t -> lch;
    		if(l) l -> par = nullptr;
    		t -> lch = nullptr;
    		update(t);
    		return make_pair(l, t);
    	}

    	pair<np, np> split2(np t) { //move t to top and detach t
    		splay(t);
    		np l = t -> lch;
    		np r = t -> rch;
    		if(l) l -> par = nullptr;
    		t -> lch = nullptr;
    		if(r) r -> par = nullptr;
    		t -> rch = nullptr;
    		update(t);
    		return make_pair(l, r);
    	}

    	tuple<np, np, np> split(np u, np t) {// u) (u  t) (t    /    t) (t  u) (u
    	    auto v = split2(u);
    		if(same(v.first, t)) {
    			auto r = split2(t);
    			return make_tuple(r.first, r.second, v.second);
    		} else {
    			auto r = split2(t);
    			return make_tuple(v.first, r.first, r.second);
    		}
    	}
    
    	np get_node(int s, int t) {
    		if(ptr[s].find(t) == ptr[s].end())ptr[s][t] = new node_t(s, t);
    		return ptr[s][t];
    	}
    
    	np root(np t) {
    		if(!t) return t;
    		while(t -> par) t = t -> par;
    		return t;
    	}

    	np reroot(np t) {
    		auto[l, r] = split(t);
    		return merge(r, l);
    	}
		int size(np u) { // NEW!
			splay(u);
			return u -> sz;
		}
    
    	bool same(np u, np v) {
    		if(u) splay(u);
    		if(v) splay(v);
    		return root(u) == root(v);
    	}
		euler_tour_tree(){}
    	
    	euler_tour_tree(int _sz) : sz(_sz) {
    	    ptr.resize(sz);
    	}

    	bool same(int u, int v) {
    		return same(get_node(u, u), get_node(v, v));
    	}

    	bool link(int u, int v) {
    		if(same(u, v)) return false;
    		merge(merge(merge(reroot(get_node(u, u)), get_node(u, v)), reroot(get_node(v, v))), get_node(v, u));
    		return true;
    	}

    	bool cut(int u, int v) {
    		if(ptr[u].find(v) == ptr[u].end()) return false;
    		auto [i, j, k] = split(get_node(u, v), get_node(v, u));
    		merge(i, k);
    		np p = ptr[u][v];
    		np q = ptr[v][u];
    		ptr[u].erase(v);
    		ptr[v].erase(u);
    		delete p; delete q;
    		return true;
    	}
		int size(int u) {
			return size(get_node(u, u));
		}
		void have_auxedge_update(int t, bool b) {
			np v = get_node(t, t);
			splay(v);
			v -> have_auxedge = b;
			update_only_child_have_auxedge(v);
		}

    };

    int sz;
	int dep;
    vector<euler_tour_tree> ett;
	vector<vector<unordered_set<int>>> aux_edges;
    using np = typename euler_tour_tree::node_t*;

	private:
	 

	    void edge_level_increment(int t, int k) {
			np v = ett[k].get_node(t, t);
			ett[k].splay(v);
			auto dfs = [&](auto f, np now) -> void {
				if(now->exact) {
					ett[k].splay(now);
					now -> exact = 0;
					ett[k].update_only_child_exact(now);
					ett[k+1].link(now -> s, now -> t);
					return;
				}
				if(now -> lch && now -> lch -> child_exact) f(f, now -> lch);
				else f(f, now -> rch);
				return;
			};

			while(v && v -> child_exact) {
				dfs(dfs, v);
				ett[k].splay(v);
			}
		}
	    
		bool try_reconnect(int u, int v, int k) {
			for(int i=0; i<k;i++) {
				ett[i].cut(u, v);
			}

			for(int i=k; i >=0; i--) {
				if(ett[i].size(u) > ett[i].size(v)) swap(u, v);
				edge_level_increment(u, i);

				np t = ett[i].get_node(u, u);
				ett[i].splay(t);
				auto dfs = [&](auto f, np now) -> bool {
					if(now -> have_auxedge) {
						ett[i].splay(now);
						for(auto itr = aux_edges[i][now->s].begin(); itr != aux_edges[i][now->s].end(); ) {
							auto y = *itr;
							itr = aux_edges[i][now->s].erase(itr);
							aux_edges[i][y].erase(now->s);
							if(aux_edges[i][now->s].size()==0)ett[i].have_auxedge_update(now->s, 0);
							if(aux_edges[i][y].size()==0)ett[i].have_auxedge_update(y, 0);
							if(ett[i].same(now->s, y)) {//辺の先が自分の連結成分
							    aux_edges[i+1][now->s].insert(y);
								aux_edges[i+1][y].insert(now->s);
								if(aux_edges[i+1][now->s].size()==1)ett[i+1].have_auxedge_update(now->s, 1);
								if(aux_edges[i+1][y].size()==1)ett[i+1].have_auxedge_update(y, 1);
							}
							else {
								for(int j = 0; j <= i; j++) {
									ett[j].link(now->s, y);
								}
								return 1;
							}
						}
						return 0;
					}

					if(now -> lch && now -> lch -> child_have_auxedge) return f(f, now -> lch);
					else return f(f, now -> rch);
				};

				while(t -> child_have_auxedge) {
					if(dfs(dfs, t)) return 1;
					ett[i].splay(t);
				}
			}
			return 0;
		}

	public:
	    dynamic_connectivity(int _sz) : sz(_sz) {
			dep = 1;
			ett.emplace_back(sz);
			aux_edges.emplace_back(sz);
		}


		bool same(int u, int v) {
			return ett[0].same(u, v);
		}

		bool link(int u, int v) {
			if(u==v)return false;
			if(ett[0].link(u, v)) return true;
			aux_edges[0][u].insert(v);
			aux_edges[0][v].insert(u);
			if(aux_edges[0][u].size()==1)ett[0].have_auxedge_update(u, 1);
			if(aux_edges[0][v].size()==1)ett[0].have_auxedge_update(v, 1);
			return false;
		}

		bool cut(int u, int v) {//切った結果、分断されたか？
			for(int i=0; i<dep; i++) {
				aux_edges[i][u].erase(v);
				aux_edges[i][v].erase(u);
				if(aux_edges[i][u].size()==0)ett[i].have_auxedge_update(u, 0);
				if(aux_edges[i][v].size()==0)ett[i].have_auxedge_update(v, 0);
			}

			for(int i=dep-1; i >=0; i--) {
				if(ett[i].cut(u, v)) {
					if(dep-1==i) {
						dep++;
						ett.emplace_back(sz);
						aux_edges.emplace_back(sz);
					}
					return !try_reconnect(u, v, i);
				}
			}
			return false;
		}





};
    
int main() {
	int N, K;
	cin >> N >> K;
	dynamic_connectivity<char> tr(N);
	while(K--) {
		int type;
		cin >> type;
		int u, v;
		cin >> u >> v;
		if(type==1) {
			tr.link(u, v);
		}
		else if(type==2) {
			tr.cut(u, v);
		}
		else {
			if(tr.same(u,v)) {
				cout << "YES" << endl;
			}
			else {
				cout << "NO" << endl;
			}
		}
	}



}
accuracy: 1
time_ms: 230
memory_kb: 77512
final_rank: 14