#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for(int i = 0; i < (n); ++i)
#define Sort(a) sort(a.begin(), a.end())
#define RSort(a) sort(a.rbegin(), a.rend())
#define Output(a) cout << a << endl

typedef long long int ll;
typedef vector<int> vi;
typedef vector<long long> vll;

const int INF = 1<<30;
const ll MOD = 1000000007;

template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

/* 二次元配列
vector<vector<Type>> vv(n, vector<Type>(m, d));

vector<vector<ll>> vv(n, vector<ll>(m, d));
*/

struct UnionFind {
    vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

    UnionFind(int N) : par(N) { //最初は全てが根であるとして初期化
        for(int i = 0; i < N; i++) par[i] = i;
    }

    int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
        if (par[x] == x) return x;
        return par[x] = root(par[x]);
    }

    void unite(int x, int y) { // xとyの木を併合
        int rx = root(x); //xの根をrx
        int ry = root(y); //yの根をry
        if (rx == ry) return; //xとyの根が同じ(=同じ木にある)時はそのまま
        par[rx] = ry; //xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
    }

    bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
        int rx = root(x);
        int ry = root(y);
        return rx == ry;
    }
};

int main(){
    ll n, q; cin >> n >> q;

    UnionFind tree(n);
    vector<unordered_map<ll, ll>> cn(n);
    int c;
    rep(i, n){
        cin >> c;
        c--;
        cn[i][c] = 1;
    }

    rep(i, q){
        ll a, x, y;
        cin >> a >> x >> y;
        x -= 1;
        y -= 1;
        if(a == 1){
            ll root_x = tree.root(x);
            ll root_y = tree.root(y);
            if(!tree.same(root_x, root_y)){

                // マージテク (要素の大きさが小さい方を移動させることで計算量を少なくする。)
                if(cn[tree.root(y)].size() < cn[tree.root(x)].size()) swap(root_x, root_y);
                
                tree.unite(root_x, root_y); // yが根になる
                for(auto [key, value] : cn[root_x]){
                    cn[tree.root(y)][key] += value;
                }
            }
        }else{ // a == 2
            Output(cn[tree.root(x)][y]);
        }
    }
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define FASTIO ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
#define all(x) (x).begin(), (x).end()
#define pb push_back
#define pii pair<int, int>
#define ff first
#define ss second
#define PI acos(-1)
#define ld long double
const int mod = 1e9+7, N = 2e5+5;
int msb(int val){return sizeof(int)*8-__builtin_clzll(val);}
int n, m, k;
struct dsu{
	vector<int> p, rnk;
	dsu(int n){
		p.assign(n, 0);
		rnk.assign(n,0);
		for(int i=0;i<n;i++)p[i]=i;
	}
	int par(int u){
		if(p[u]==u)return u;
		else return p[u] = par(p[u]);
	}
	int merge(int u, int v){
		u = par(u);
		v = par(v);
		if(rnk[u] > rnk[v]){
			p[v] = u;
			rnk[u]++;
			return u;
		}else {
			p[u] = v;
			rnk[v]++;
			return v;
		}
	}
};
int a[N];
map<int, int> t[N];
void solve(int test_case){
	int i, j, q;
	cin >> n >> q;
	for(i=0;i<n;i++){
		cin >> a[i];
		a[i]--;
		t[i][a[i]]=1;
	}
	dsu d(n);
	while(q--){
		int typ, x, y;
		cin >> typ >> x >> y;
		x--,y--;
		if(typ == 1){
			x = d.par(x), y = d.par(y);
			if(x == y)continue;
			int u = d.merge(x, y);
			if(u != x)swap(x, y);
			for(auto [f, s] : t[y])t[u][f] += s;
		}else {
			cout << t[d.par(x)][y] << '\n';
		}
	}
}

signed main(){
	FASTIO;
#define MULTITEST 0
#if MULTITEST
	int ___T;
	cin >> ___T;
	for(int T_CASE = 1; T_CASE <= ___T; T_CASE++)
		solve(T_CASE);
#else
	solve(1);
#endif
	return 0;
}
