// Original C++ solution (slower):
#include <bits/stdc++.h>



using namespace std;



#define int long long

#define all(v) begin(v), end(v)

#define rep(i, n) for(int i = 0; i < (int)(n); i++)

#define reps(i, s, n) for(int i = (int)(s); i < (int)(n); i++)

#define min(...) min({__VA_ARGS__})

#define max(...) max({__VA_ARGS__})



template<class T1, class T2> void chmin(T1 &a, T2 b){if(a>b)a=b;}

template<class T1, class T2> void chmax(T1 &a, T2 b){if(a<b)a=b;}



using pint = pair<int, int>;

using tint = tuple<int, int, int>;

using vint = vector<int>;



const int inf = 1LL << 55;

const int mod = 1e9 + 7;



#define MAX_N 100000

#define MAX_Q 100000



struct SegmentTree {

  vector< array<int, 10> > data, lazy;

  array<int, 10> ini{{0,1,2,3,4,5,6,7,8,9}};

  int sz;

  SegmentTree(){}

  SegmentTree(int n) {

    sz = 1; while(sz < n) sz <<= 1;

    data.resize(2*sz-1, array<int, 10>{0});

    lazy.resize(2*sz-1, ini);

    rep(i, n) data[i+sz-1][0] = 1;

    for(int k = sz-2; k >= 0; k--)

      data[k][0] = data[2*k+1][0] + data[2*k+2][0];

  }

  void push(int k, int l, int r) {

    int tmp[10];

    rep(i, 10) tmp[i] = data[k][i], data[k][i] = 0;

    rep(i, 10) data[k][lazy[k][i]] += tmp[i];

    if(r - l > 1) {

      array<int, 10> conv = ini;

      rep(i, 10) conv[i] = lazy[k][i];

      rep(i, 10) {

	lazy[2*k+1][i] = conv[lazy[2*k+1][i]];

	lazy[2*k+2][i] = conv[lazy[2*k+2][i]];

      }

    }

    lazy[k] = ini;

  }

  void update(int a, int b, int x, int y, int k, int l, int r) {

    push(k, l, r);

    if(r <= a || b <= l) return;

    if(a <= l && r <= b) {

      rep(i, 10) if(lazy[k][i] == x) lazy[k][i] = y;

      push(k, l, r);

      return;

    }

    update(a, b, x, y, 2*k+1, l, (l+r)/2);

    update(a, b, x, y, 2*k+2, (l+r)/2, r);

    rep(i, 10) data[k][i] = data[2*k+1][i] + data[2*k+2][i];

  }

  void update(int a, int b, int x, int y) {

    update(a, b, x, y, 0, 0, sz);

  }

  int query(int a, int b, int x, int y, int k, int l, int r) {

    push(k, l, r);

    if(r <= a || b <= l) return 0;

    if(a <= l && r <= b) {

      int res = 0;

      for(int i = x; i <= y; i++) res += data[k][i];

      return  res;

    }

    int vl = query(a, b, x, y, 2*k+1, l, (l+r)/2);

    int vr = query(a, b, x, y, 2*k+2, (l+r)/2, r);

    return vl + vr;

  }

  int query(int a, int b, int x, int y) {

    return query(a, b, x, y, 0, 0, sz);

  }

};



int n, q;

vector<int> graph[MAX_N];

int li[MAX_N], ri[MAX_N], idx;



SegmentTree seg;



void dfs(int u) {

  li[u] = idx++;

  for(int v : graph[u]) dfs(v);

  ri[u] = idx;

}



signed main()

{

  cin.tie(0);

  ios_base::sync_with_stdio(0);

  cout << fixed << setprecision(12);



  cin >> n >> q;

  rep(i, n-1) {

    int u, v;

    cin >> u >> v;

    graph[u].push_back(v);

  }

  memset(li, -1, sizeof(li));

  memset(ri, -1, sizeof(ri));

  idx = 0;

  dfs(0);



  seg = SegmentTree(n);

  rep(i, q) {

    int t, r, x, y;

    cin >> t >> r >> x >> y;

    if(t == 1) cout << seg.query(li[r], ri[r], x, y) << endl;

    else seg.update(li[r], ri[r], x, y);

  }



  return 0;

}




// Optimized solution to the same problem (faster):
#include <bits/stdc++.h>



using namespace std;



/*

struct Monoid {

  using T = _type_;

  static T op(const T& a, const T& b) {return _a_op_b_;}

  static constexpr T e() {return _identity_element_;};

};

*/



/*

struct M_act {

  using OS = _operand_set_(i.e. Monoid)_;

  using OM = _operator_monoid_(i.e. Monoid)_;

  using T = OS::T;

  using M = OM::T;

  static T act(const int& len, const M& m, const T& a) {return _m_act_a_}

};

*/



template<class M_act>

struct RUSegmentTree {

  using OS = typename M_act::OS;

  using OM = typename M_act::OM;

  using T = typename OS::T;

  using M = typename OM::T;

  vector<T> data;

  vector<M> lazy;

  int sz;

  RUSegmentTree(){}

  RUSegmentTree(int n){init(n);}

  RUSegmentTree(vector<T> _data) {

    init(_data.size());

    build(_data);

  }

  void init(int n) {

    sz = 1; while(sz < n) sz <<= 1;

    data.resize(2*sz-1, OS::e());

    lazy.resize(2*sz-1, OM::e());

  }

  void build(const vector<T>& _data) {

    for(int i = 0; i < (int)_data.size(); ++i) data[i+sz-1] = _data[i];

    for(int i = sz-2; i >= 0; --i) data[i] = OS::op(data[2*i+1], data[2*i+2]);

  }

  void push(int k, int len) {

    if(lazy[k] == OM::e()) return;

    if(len > 1) {

      lazy[2*k+1] = OM::op(lazy[k], lazy[2*k+1]);

      lazy[2*k+2] = OM::op(lazy[k], lazy[2*k+2]);

    }

    data[k] = M_act::act(len, lazy[k], data[k]);

    lazy[k] = OM::e();

  }

  void update(int a, int b, const M& x, int k, int l, int r) {

    push(k, r-l);

    if(r <= a || b <= l) return;

    if(a <= l && r <= b) {

      lazy[k] = OM::op(x, lazy[k]);

      push(k, r-l);

      return;

    }

    update(a, b, x, 2*k+1, l, (l+r)/2);

    update(a, b, x, 2*k+2, (l+r)/2, r);

    data[k] = OS::op(data[2*k+1], data[2*k+2]);

  }

  void update(int a, int b, const T& x) {

    update(a, b, x, 0, 0, sz);

  }

  T query(int a, int b, int k, int l, int r) {

    push(k, r-l);

    if(r <= a || b <= l) return OS::e();

    if(a <= l && r <= b) return data[k];

    T vl = query(a, b, 2*k+1, l, (l+r)/2);

    T vr = query(a, b, 2*k+2, (l+r)/2, r);

    return OS::op(vl, vr);

  }

  T query(int a, int b) {

    return query(a, b, 0, 0, sz);

  }

};



struct Data {

  using T = array<int, 10>;

  static T op(const T& a, const T& b) {

    T res = e();

    for(int i = 0; i < 10; ++i) res[i] = a[i]+b[i];

    return res;

  }

  static T e() {

    static T ini = array<int, 10>{0};

    return ini;

  }

};



struct Operator {

  using T = array<int, 10>;

  static T op(const T& a, const T& b) {

    T res = e();

    for(int i = 0; i < 10; ++i) {

      res[i] = a[b[i]];

    }

    return res;

  }

  static T e() {

    static T ini = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    return ini;

  }

};



struct TreeLightMonoid {

  using OS = Data;

  using OM = Operator;

  using T = OS::T;

  using M = OM::T;

  static T act(const int& len, const M& m, const T& a) {

    T res = OS::e();

    for(int i = 0; i < 10; ++i) {

      res[m[i]] += a[i];

    }

    return res;

  }

};



using Graph = vector<vector<int> >;



int main() {

  int n, q;

  cin >> n >> q;

  Graph graph(n);

  for(int i = 0; i < n-1; ++i) {

    int u, v;

    cin >> u >> v;

    graph[u].push_back(v);

  }



  vector<int> L(n), R(n);

  int idx = 0;

  function<void(int)> dfs = [&](int u) {

    L[u] = idx++;

    for(int v : graph[u]) dfs(v);

    R[u] = idx;

  };

  dfs(0);



  vector<array<int, 10> > data(n, array<int, 10>{1, 0});

  RUSegmentTree<TreeLightMonoid> seg(data);

  while(q--) {

    int t, r, x, y;

    cin >> t >> r >> x >> y;

    if(t == 1) {

      auto res = seg.query(L[r], R[r]);

      int ans = 0;

      for(int i = x; i <= y; ++i) {

	ans += res[i];

      }

      cout << ans << endl;

    } else {

      auto tmp = Operator::e();

      tmp[x] = y;

      seg.update(L[r], R[r], tmp);

    }

  }



  return 0;

}




// Fastest known solution for this problem: