#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5;
const int M = 5e3 + 5;
const int INF = INT_MAX;
const int Meet_point = 35000;
int tot = 1, cnt[N], n, m, head[N], dep[N], ncnt, ite[N];
queue<int> q;
struct Edge {
  int to, nxt, cap;
} e[N];
int ans[N];
struct Weapons {
  int type, l, r, siz, a, b, c;
} wea[M];
int tree[N];
int read() {
  int x = 0, f = 1;
  char c = getchar();
  while (c < '0' || c > '9') {
    if (c == '-') f = -1;
    c = getchar();
  }
  while (c >= '0' && c <= '9') {
    x = (x << 1) + (x << 3) + (c ^ 48);
    c = getchar();
  }
  return x * f;
}
void addedge(int u, int v, int cap) {
  e[++tot] = (Edge){v, head[u], cap};
  head[u] = tot;
  e[++tot] = (Edge){u, head[v], 0};
  head[v] = tot;
}
void BuildTree(int u, int l, int r) {
  if (l == r) {
    tree[u] = l;
    return;
  }
  tree[u] = ++ncnt;
  int mid = (l + r) >> 1;
  BuildTree(u * 2, l, mid);
  BuildTree(u * 2 + 1, mid + 1, r);
  addedge(tree[u], tree[u * 2], INF);
  addedge(tree[u], tree[u * 2 + 1], INF);
}
void Modify(int l, int r, int u, int id, int &L, int &R) {
  if (L <= l && r <= R) {
    addedge(id, tree[u], 1);
    return;
  }
  int mid = (l + r) >> 1;
  if (L <= mid) Modify(l, mid, u * 2, id, L, R);
  if (R > mid) Modify(mid + 1, r, u * 2 + 1, id, L, R);
}
void BFS() {
  dep[0] = 1;
  q.push(0);
  while (!q.empty()) {
    int x = q.front();
    q.pop();
    for (int i = head[x]; i; i = e[i].nxt) {
      int y = e[i].to;
      if (e[i].cap && !dep[y]) {
        dep[y] = dep[x] + 1;
        q.push(y);
      }
    }
  }
}
int DFS(int u, int fl) {
  if (!fl || u == Meet_point) return fl;
  for (int &i = ite[u]; i; i = e[i].nxt) {
    int x = e[i].to;
    if (e[i].cap && dep[u] < dep[x]) {
      int tmp = DFS(x, min(fl, e[i].cap));
      if (tmp) {
        e[i].cap -= tmp;
        e[i ^ 1].cap += tmp;
        return tmp;
      }
    }
  }
  return 0;
}
int Dinic() {
  for (int f, flow = 0;;) {
    memset(dep, 0, sizeof(dep));
    BFS();
    if (!dep[Meet_point]) return flow;
    memcpy(ite, head, sizeof(ite));
    while (f = DFS(0, INF)) flow += f;
  }
}
void Find_Route(int cur, int &s, int pre) {
  if (s) return;
  if (cur > m && cur <= n + m) {
    s = cur - m;
    return;
  }
  for (int i = head[cur]; i; i = e[i].nxt) {
    if (e[i].to != pre && e[i].cap) {
      Find_Route(e[i].to, s, cur);
      --e[i].cap;
      return;
    }
  }
}
int main() {
  n = read(), m = read();
  ncnt = n + m;
  BuildTree(1, 1, m);
  for (int i = 1; i <= n; i++) {
    wea[i].type = read();
    if (wea[i].type == 0) {
      wea[i].siz = read();
      addedge(0, i + m, 1);
      for (int j = 1; j <= wea[i].siz; j++) {
        int x = read();
        addedge(i + m, x, 1);
      }
    } else if (wea[i].type == 1) {
      addedge(0, i + m, 1);
      wea[i].l = read(), wea[i].r = read();
      Modify(1, m, 1, i + m, wea[i].l, wea[i].r);
    } else {
      addedge(0, i + m, 2);
      wea[i].a = read();
      wea[i].b = read();
      wea[i].c = read();
      addedge(i + m, wea[i].a, 1);
      addedge(i + m, wea[i].b, 1);
      addedge(i + m, wea[i].c, 1);
    }
  }
  for (int i = 1; i <= m; i++) addedge(i, Meet_point, 1);
  int Max_Flow = Dinic();
  cout << Max_Flow << endl;
  for (int i = head[Meet_point]; i; i = e[i].nxt) {
    Find_Route(e[i].to, ans[e[i].to], Meet_point);
  }
  for (int i = 1; i <= m; i++) ++cnt[ans[i]];
  for (int i = 1; i <= n; i++) {
    if (wea[i].type == 2 && cnt[i] == 1) {
      if (ans[wea[i].a] != i)
        ans[wea[i].a] = i;
      else
        ans[wea[i].b] = i;
    }
  }
  for (int i = 1; i <= m; i++)
    if (ans[i]) printf("%d %d\n", ans[i], i);
  return 0;
}
