#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 500005;
struct Info {
int x;
ll cnt;
Info() {
x = cnt = 0;
}
Info(int a) {
x = a;
cnt = 1;
}
Info(int a, ll b) {
x = a;
cnt = b;
}
};
Info operator + (const Info & a, const Info & b) {
if (a.x == b.x) {
return Info(a.x, a.cnt + b.cnt);
} else {
if (a.cnt >= b.cnt) {
return Info(a.x, a.cnt - b.cnt);
} else {
return Info(b.x, b.cnt - a.cnt);
}
}
}
Info & operator += (Info & a, const Info & b) {
return a = a + b;
}
struct SegmentTreeNode {
int ch[2], sz;
Info w;
} segmenttreenode[N * 50];
int root[N * 2], segmenttreenodecnt;
void push_up(int p) {
segmenttreenode[p].sz = segmenttreenode[segmenttreenode[p].ch[0]].sz + segmenttreenode[segmenttreenode[p].ch[1]].sz;
segmenttreenode[p].w = segmenttreenode[segmenttreenode[p].ch[0]].w + segmenttreenode[segmenttreenode[p].ch[1]].w;
}
void insert(int & p, int x, int d = 1, int l = 0, int r = 1000000) {
if (p == 0) {
segmenttreenodecnt++;
p = segmenttreenodecnt;
}
if (l == r) {
segmenttreenode[p].w.x = x;
segmenttreenode[p].w.cnt += d;
segmenttreenode[p].sz += d;
} else {
int mid = (l + r) / 2;
if (x <= mid) {
insert(segmenttreenode[p].ch[0], x, d, l, mid);
} else {
insert(segmenttreenode[p].ch[1], x, d, mid + 1, r);
}
push_up(p);
}
}
int query(int p, int x, int l = 0, int r = 1000000) {
if (p == 0) {
return 0;
}
if (l == r) {
return segmenttreenode[p].w.cnt;
} else {
int mid = (l + r) / 2;
if (x <= mid) {
return query(segmenttreenode[p].ch[0], x, l, mid);
} else {
return query(segmenttreenode[p].ch[1], x, mid + 1, r);
}
}
}
void merge(int & x, int y, int l = 0, int r = 1000000) {
if (x == 0 || y == 0) {
x |= y;
} else if (l == r) {
segmenttreenode[x].w += segmenttreenode[y].w;
segmenttreenode[x].sz += segmenttreenode[y].sz;
} else {
int mid = (l + r) / 2;
merge(segmenttreenode[x].ch[0], segmenttreenode[y].ch[0], l, mid);
merge(segmenttreenode[x].ch[1], segmenttreenode[y].ch[1], mid + 1, r);
push_up(x);
}
}
struct LinkListNode {
int val, nxt;
} linklistnode[N * 2];
int head[N * 2], tail[N * 2], linklistnodecnt;
int opt3_x[N];
int main() {
// freopen("in.txt", "r", stdin);
int n, q;
scanf("%d %d", &n, &q);
for (int l, i = 1; i <= n; i++) {
scanf("%d", &l);
if (l != 0) {
head[i] = linklistnodecnt + 1;
for (int x; l != 0; l--) {
scanf("%d", &x);
linklistnodecnt++;
linklistnode[linklistnodecnt].nxt = tail[i];
linklistnode[linklistnodecnt].val = x;
tail[i] = linklistnodecnt;
insert(root[i], x);
}
}
}
for (int op, x, y, m, x1, x2, x3; q != 0; q--) {
scanf("%d", &op);
if (op == 1) {
scanf("%d %d", &x, &y);
insert(root[x], y);
linklistnodecnt++;
linklistnode[linklistnodecnt].nxt = tail[x];
linklistnode[linklistnodecnt].val = y;
tail[x] = linklistnodecnt;
if (head[x] == 0) {
head[x] = linklistnodecnt;
}
} else if (op == 2) {
scanf("%d", &x);
if (linklistnode[tail[x]].val == 0) {
return 114514;
}
insert(root[x], linklistnode[tail[x]].val, -1);
tail[x] = linklistnode[tail[x]].nxt;
if (tail[x] == 0) {
head[x] = 0;
}
} else if (op == 3) {
scanf("%d", &m);
Info res;
ll tot_len = 0;
for (int i = 1; i <= m; i++) {
scanf("%d", &opt3_x[i]);
tot_len += segmenttreenode[root[opt3_x[i]]].sz;
res += segmenttreenode[root[opt3_x[i]]].w;
}
ll cnt = 0;
for (int i = 1; i <= m; i++) {
cnt += query(root[opt3_x[i]], res.x);
}
if (cnt > tot_len / 2) {
printf("%d\n", res.x);
} else {
printf("-1\n");
}
} else {
scanf("%d %d %d", &x1, &x2, &x3);
if (head[x1] != 0 && head[x2] != 0) {
if (head[x1] == 0) {
head[x3] = head[x2];
tail[x3] = tail[x2];
root[x3] = root[x2];
} else if (head[x2] == 0) {
head[x3] = head[x1];
tail[x3] = tail[x1];
root[x3] = root[x1];
} else {
merge(root[x1], root[x2]);
root[x3] = root[x1];
head[x3] = head[x1];
tail[x3] = tail[x2];
linklistnode[head[x2]].nxt = tail[x1];
}
}
}
}
return 0;
}