#include <iostream>
#include <iomanip>
#include <sstream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
#include <complex>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <cassert>
#include <climits>
#include <queue>
#include <set>
#include <map>
#include <valarray>
#include <bitset>
#include <stack>
using namespace std;

#define REP(i,n) for(int i=0;i<(int)n;++i)
#define FOR(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();++i)
#define ALL(c) (c).begin(), (c).end()
typedef long long ll;
typedef pair<int,int> pii;
const int INF = 1<<29;
const double PI = acos(-1);
const double EPS = 1e-8;

struct UnionFind {
  vector<int> data;
  UnionFind(int size) : data(size, -1) { }
  bool unionSet(int x, int y) {
    x = root(x); y = root(y);
    if (x != y) {
      if (data[y] < data[x]) swap(x, y);
      data[x] += data[y]; data[y] = x;
    }
    return x != y;
  }
  bool findSet(int x, int y) {
    return root(x) == root(y);
  }
  int root(int x) {
    return data[x] < 0 ? x : data[x] = root(data[x]);
  }
  int size(int x) {
    return -data[root(x)];
  }
};

bool visited[400];

typedef vector<multiset<int> > Graph;
bool connected(const Graph &g, int a, int b) {
  queue<int> Q;
  REP(i,g.size()) visited[i] = 0;
  Q.push(a);
  visited[a] = 1;
  while(!Q.empty()) {
    int now = Q.front(); Q.pop();
    if (now == b) return 1;
    FOR(it, g[now]) {
      if (!visited[*it]) {
        visited[*it] = 1;
        Q.push(*it);
      }
    }
  }
  return 0;
}

int Q[40000];
int U[40000];
int V[40000];

int main() {
  int n, K;
  cin >> n >> K;
  REP(i,K) {
    cin >> Q[i] >> U[i] >> V[i];
  }
  int rk = sqrt(K)+1;
  for (int i=0; i<K; i+=rk) {
    int upper = min(i+rk, K);
    // 関係するノードを列挙
    set<int> relate;
    for (int j=i; j<upper; ++j) {
      relate.insert(U[j]);
      relate.insert(V[j]);
    }
    // i-1までのクエリによって作られるノードの連結情報を作る
    // ただし、関係するノード同士の辺は考えない
    // この連結情報は不変
    UnionFind uf(n);
    set<pii> st;
    for (int j=i-1; j>=0; --j) {
      int a = U[j];
      int b = V[j];
      if (relate.count(a) == 1 && relate.count(b) == 1)continue;
      if (Q[j] == 1) {
        if (st.count(pii(a,b)) == 0) {
          uf.unionSet(a,b);
        }
      } else if (Q[j] == 2) {
        st.insert(pii(a,b));
      }
    }
    // 連結成分のmapを作る
    map<int,int> mp;
    int m = 0;
    for (int j=i; j<upper; ++j) {
      if (mp.count(uf.root(U[j])) == 0) mp[uf.root(U[j])] = m++;
      if (mp.count(uf.root(V[j])) == 0) mp[uf.root(V[j])] = m++;
    }
    // i-1までのクエリによって作られる、連結成分の接続関係を表すグラフを作る
    Graph g(m);
    st.clear();
    for (int j=i-1; j>=0; --j) {
      int a = U[j];
      int b = V[j];
      if (relate.count(a) == 1 && relate.count(b) == 1) {
        if (Q[j] == 1) {
          if (st.count(pii(a,b)) == 0) {
            int A = mp[uf.root(a)];
            int B = mp[uf.root(b)];
            g[A].insert(B);
            g[B].insert(A);
          }
        } else if (Q[j] == 2) {
          st.insert(pii(a,b));
        }
      }
    }    
    // cout << "i = " << i << endl;
    // REP(j,n) cout << uf.root(j) << " "; cout << endl;
    for (int j=i; j<upper; ++j) {
      int a = mp[uf.root(U[j])];
      int b = mp[uf.root(V[j])];
      if (Q[j] == 1) {
        g[a].insert(b);
        g[b].insert(a);
      } else if (Q[j] == 2) {
        g[a].erase(g[a].find(b));
        g[b].erase(g[b].find(a));
        // g[a].erase(b);
        // g[b].erase(a);
      } else {
        // cout << "a" << j << endl;
        // cout << a << " " << b << " " << m << endl;
        if (connected(g,a,b)) {
          puts("YES");
        } else {
          puts("NO");
        }
      }
    }
  }
}

Measured memory_bytes: 1956