#include <bits/stdc++.h>
using namespace std;
template <class T>
struct rge {
  T b, e;
};
template <class T>
rge<T> range(T i, T j) {
  return rge<T>{i, j};
}
template <class T>
auto dud(T *x) -> decltype(cerr << *x, 0);
template <class T>
char dud(...);
struct debug {
  template <class T>
  debug &operator<<(const T &) {
    return *this;
  }
};
const int N = 1e5 + 5;
const int M = N;
const int inf = 1e9 + 7;
struct dsu {
  vector<int> pr;
  dsu(int n) : pr(n + 5) { iota(pr.begin(), pr.end(), 0); }
  int find(int u) { return pr[u] == u ? u : pr[u] = find(pr[u]); }
  void unite(int u, int v) {
    u = find(u), v = find(v);
    if (u != v) {
      pr[v] = u;
      debug() << "the boss of"
                 " "
              << "v"
                 ": "
              << (v)
              << " "
                 "is"
                 " "
              << "u"
                 ": "
              << (u) << " ";
    }
  }
};
struct query {
  int t, u, v, doc;
};
vector<int> graph[N];
int pr[N], dep[N], tot[N], heavy[N];
int head[N], pos[N], who[N];
set<pair<int, int> > st[N];
vector<query> queries;
int indeg[N];
int timer = 0;
int n, m;
int dfs(int u) {
  tot[u] = 1, heavy[u] = head[u] = 0;
  for (int v : graph[u])
    if (v != pr[u]) {
      pr[v] = u;
      dep[v] = dep[u] + 1;
      tot[u] += dfs(v);
      if (tot[v] > tot[heavy[u]]) heavy[u] = v;
    }
  return tot[u];
}
void hld(int u, int h) {
  head[u] = h, pos[u] = ++timer, who[timer] = u;
  if (heavy[u] != 0) hld(heavy[u], h);
  for (int v : graph[u])
    if (v != pr[u] && v != heavy[u]) hld(v, v);
}
template <class F>
void process_path(int u, int v, F fun) {
  for (; head[u] != head[v]; v = pr[head[v]]) {
    if (dep[head[u]] > dep[head[v]]) swap(u, v);
    fun(pos[head[v]], pos[v]);
  }
  if (dep[u] > dep[v]) swap(u, v);
  if (u != v) fun(pos[heavy[u]], pos[v]);
  fun(pos[u], pos[u]);
}
void update_path(int u, int v, int doc) {
  debug() << "sign from"
             " "
          << "u"
             ": "
          << (u)
          << " "
             "to"
             " "
          << "v"
             ": "
          << (v)
          << " "
             " "
          << "doc"
             ": "
          << (doc) << " ";
  process_path(u, v, [&](int l, int r) { st[doc].insert({l, r}); });
}
int main() {
  ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  int doc = 0;
  for (auto i = (0) - ((0) > (m)); i != m - ((0) > (m));
       i += 1 - 2 * ((0) > (m))) {
    query q;
    cin >> q.t >> q.u;
    if (q.t == 1) {
      cin >> q.v;
      swap(q.u, q.v);
      graph[q.u].push_back(q.v);
      indeg[q.v]++;
    } else if (q.t == 2) {
      q.doc = ++doc;
      st[doc].insert({-inf, -inf});
      st[doc].insert({inf, inf});
    } else if (q.t == 3)
      cin >> q.doc;
    queries.push_back(q);
  }
  for (auto u = (1) - ((1) > (n + 1)); u != n + 1 - ((1) > (n + 1));
       u += 1 - 2 * ((1) > (n + 1)))
    if (indeg[u] == 0) {
      dfs(u);
      hld(u, u);
    }
  dsu boss(n);
  for (auto &q : queries) {
    if (q.t == 1)
      boss.unite(q.u, q.v);
    else if (q.t == 2)
      update_path(q.u, boss.find(q.u), q.doc);
    else {
      debug() << " "
              << "st[q.doc]"
                 ": "
              << (st[q.doc]) << " ";
      int k = pos[q.u];
      auto it = --st[q.doc].upper_bound({k, inf});
      debug() << " "
              << "k"
                 ": "
              << (k)
              << " "
                 " "
              << "*it"
                 ": "
              << (*it) << " ";
      for (auto jt : {prev(it), it, next(it)})
        if (jt != st[q.doc].end())
          if (jt->first <= k && k <= jt->second) {
            k = inf;
            break;
          }
      cout << (k == inf ? "YES" : "NO") << '\n';
    }
  }
  return 0;
}
