#include <iostream>
#include <sstream>
#include <set>
#include <fstream>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <vector>
#include <map>
#include <queue>
#include <numeric>
#include <string>
#include <cmath>
#include <climits>
#include <stack>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <array>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <bitset>
#include <cassert>
#include <tuple>
#include <iterator>
#include <random>
#include <chrono>
#include <list>
using namespace std;
typedef long long ll;
typedef long double ld;
const int N = 6e4 + 5;
//const int mod=998244353;
const int mod=1e9+7;
struct Node {
int p = 0, c[2] = {0,0};
bool flip = false;
int sz = 1; // size in aux tree (not strictly necessary here but convenient)
int val = -1; // actual node value (int)
int aand = -1; // aggregated AND over the aux subtree (including this node)
Node() {}
Node(int v) {
p = c[0] = c[1] = 0;
flip = false;
sz = 1;
val = v;
aand = v;
}
};
struct LinkCutAND {
vector<Node> t;
LinkCutAND() {}
LinkCutAND(int n, const vector<int>& vals = {}) {
t.assign(n+1, Node());
for (int i = 1; i <= n; ++i) {
int v = (i < (int)vals.size()) ? vals[i] : 0;
t[i] = Node(v);
}
}
// utilities
bool is_root(int x) {
int p = t[x].p;
return p == 0 || (t[p].c[0] != x && t[p].c[1] != x);
}
int dir(int x) {
int p = t[x].p;
if (!p) return -1;
return t[p].c[1] == x;
}
void pull(int x) {
if (!x) return;
int l = t[x].c[0], r = t[x].c[1];
t[x].sz = 1;
if (l) t[x].sz += t[l].sz;
if (r) t[x].sz += t[r].sz;
// AND aggregation: neutral element is all-ones (-1)
int res = t[x].val;
if (l) res &= t[l].aand;
if (r) res &= t[r].aand;
t[x].aand = res;
}
void apply_flip(int x) {
if (!x) return;
t[x].flip ^= 1;
swap(t[x].c[0], t[x].c[1]);
}
void push(int x) {
if (!x) return;
if (t[x].flip) {
if (t[x].c[0]) apply_flip(t[x].c[0]);
if (t[x].c[1]) apply_flip(t[x].c[1]);
t[x].flip = false;
}
}
void rotate(int x) {
int p = t[x].p;
int g = t[p].p;
int dx = (t[p].c[1] == x);
int b = t[x].c[dx^1];
if (!is_root(p)) {
if (t[g].c[0] == p) t[g].c[0] = x;
else if (t[g].c[1] == p) t[g].c[1] = x;
}
t[x].p = g;
t[x].c[dx^1] = p;
t[p].p = x;
t[p].c[dx] = b;
if (b) t[b].p = p;
pull(p);
pull(x);
}
void push_path(int x) {
if (!x) return;
if (!is_root(x)) push_path(t[x].p);
push(x);
}
void splay(int x) {
if (!x) return;
push_path(x);
while (!is_root(x)) {
int p = t[x].p;
int g = t[p].p;
if (!is_root(p)) {
if ((t[p].c[0] == x) ^ (t[g].c[0] == p)) rotate(x);
else rotate(p);
}
rotate(x);
}
pull(x);
}
// access: make preferred path from root to x, returns last accessed node
int access(int x) {
int last = 0;
for (int v = x; v; v = t[v].p) {
splay(v);
// detach right child (preferred child) and attach last
t[v].c[1] = last;
pull(v);
last = v;
}
splay(x);
return last;
}
void make_root(int x) {
access(x);
apply_flip(x);
}
int find_root(int x) {
access(x);
// go to leftmost node
while (true) {
push(x);
if (t[x].c[0]) x = t[x].c[0];
else break;
}
splay(x);
return x;
}
bool connected(int u, int v) {
if (u == v) return true;
return find_root(u) == find_root(v);
}
// link u and v (create edge). requires they are in different trees
bool link(int u, int v) {
// prevent creating cycle
if (connected(u, v)) return false;
make_root(u);
// now u is root of its tree; set parent pointer to v
t[u].p = v;
// no need to update v here; access/pull will handle when needed
return true;
}
// cut edge u-v if it exists
bool cut(int u, int v) {
make_root(u);
access(v);
// now v is splayed and its left child should be u if edge existed and u was direct child
if (t[v].c[0] != u || t[u].c[1] != 0) return false;
t[v].c[0] = 0;
t[u].p = 0;
pull(v);
return true;
}
// set node u's value to x
void set_val(int u, int x) {
access(u);
t[u].val = x;
pull(u);
}
int get_val(int u) {
access(u);
return t[u].val;
}
// query bitwise AND along path u..v (includes both endpoints)
int query_and(int u, int v) {
if (!connected(u, v)) {
// if not connected, convention: return neutral element or throw.
// I'll return -1 (all bits set) if you want identity; but it's better to handle connectivity outside.
return -1;
}
make_root(u);
access(v);
// after this, v is root of aux tree that corresponds to path u..v
return t[v].aand;
}
};
map <int,vector<int>> adj;
void solve() {
int n,b;
cin>>n>>b;
vector <int> xs;
map <int,int> v;
map <int,int> fav;
vector <int> alls;
map <int,int> id;
for (int i=1;i<=n;i++) {
int x,f;
cin>>x>>f;
xs.push_back(x);
alls.push_back(x);
fav[x]=f;
adj[f].push_back(x);
adj[x].push_back(f);
int k;
cin>>k;
int val=0;
while (k--) {
int u;
cin>>u;
val|=(1<<u);
}
v[x]=val;
}
int q;
cin>>q;
vector <pair<pair<int,int>,int>> qu(q);
for (int i=0;i<q;i++) {
int type;
cin>>type;
int x;
cin>>x;
alls.push_back(x);
qu[i].first.first=type;
qu[i].first.second=x;
if (type==0) continue;
if (type==1) {
int y;
cin>>y;
qu[i].second=y;
continue;
}
int f;
cin>>f;
fav[x]=f;
adj[f].push_back(x);
adj[x].push_back(f);
int k;
cin>>k;
int val=0;
while (k--) {
int u;
cin>>u;
val|=(1<<u);
}
v[x]=val;
qu[i].second=f;
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for (int i=0;i<alls.size();i++) {
id[alls[i]]=i+1;
}
vector <int> vals(alls.size()+1);
vector <int> dead(alls.size()+1,1);
for (auto i : xs) dead[id[i]]=0;
for (int i=1;i<=alls.size();i++) vals[i]=v[alls[i-1]];
LinkCutAND lct(vals.size(),vals);
for (auto i : xs) {
if (!dead[id[fav[i]]]) lct.link(id[i],id[fav[i]]);
}
for (int i=0;i<q;i++) {
int type=qu[i].first.first;
int x=qu[i].first.second;
int y=qu[i].second;
if (type==0) {
dead[id[x]]=1;
for (auto f : adj[x]) if (!dead[id[f]]) lct.cut(id[x],id[f]);
} else if (type==1) {
int res=lct.query_and(id[x],id[y]);
if (res==-1) cout<<res<<'\n';
else cout<<__builtin_popcount(res)<<'\n';
} else {
dead[id[x]]=0;
if (!dead[id[y]]) lct.link(id[x],id[y]);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;
//cin>>t;
while (t--) solve();
return 0;
}