#include <bits/stdc++.h>
using namespace std;
#pragma warning(disable : 4996)
struct E {
  int to, i;
};
const int N = 2e4 + 5, M = 1e6, INF = 1e9, S = N - 2, T = N - 1;
vector<E> g[N];
int cp[M], fl[M], lc[N], rc[N], d[N], p[N], killedBy[N], ct[N];
int ce = 0, cv = 0;
void addEdge(int a, int b, int c) {
  cp[ce] = c;
  g[a].push_back({b, ce++});
  cp[ce] = 0;
  g[b].push_back({a, ce++});
}
void build(int v, int L, int R) {
  if (L == R) {
    addEdge(v, L, 1);
  } else {
    lc[v] = cv;
    rc[v] = cv + 1;
    int m = (L + R) >> 1;
    addEdge(v, lc[v], INF);
    addEdge(v, rc[v], INF);
    cv += 2;
    build(lc[v], L, m);
    build(rc[v], m + 1, R);
  }
}
void que(int l, int r, int from, int v, int L, int R) {
  if (l > r) return;
  if (L == l && R == r) {
    addEdge(from, v, 1);
  } else {
    int m = (L + R) >> 1;
    que(l, min(m, r), from, lc[v], L, m);
    que(max(m + 1, l), r, from, rc[v], m + 1, R);
  }
}
bool bfs() {
  memset(d, -1, sizeof(d));
  d[S] = 0;
  queue<int> q;
  q.push(S);
  while (!q.empty()) {
    int node = q.front();
    q.pop();
    for (E e : g[node]) {
      if (d[e.to] == -1 && cp[e.i] > fl[e.i]) {
        d[e.to] = d[node] + 1;
        q.push(e.to);
      }
    }
  }
  return d[T] != -1;
}
int dfs(int node, int flow) {
  if (node == T) return flow;
  for (int& i = p[node]; i < g[node].size(); i++) {
    E& e = g[node][i];
    if (d[e.to] == d[node] + 1 && cp[e.i] > fl[e.i]) {
      int ret = dfs(e.to, min(flow, cp[e.i] - fl[e.i]));
      if (ret) {
        fl[e.i] += ret;
        fl[e.i ^ 1] -= ret;
        return ret;
      }
    }
  }
  return 0;
}
int maxFlow() {
  int flow = 0;
  while (bfs()) {
    memset(p, 0, sizeof(p));
    int ret;
    while ((ret = dfs(S, INF))) {
      flow += ret;
    }
  }
  return flow;
}
vector<vector<int>> t3;
signed main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) addEdge(i, T, 1);
  cv = n + m;
  int root = cv;
  cv++;
  build(root, 0, m - 1);
  for (int i = m; i < n + m; i++) {
    int type;
    cin >> type;
    if (type == 0) {
      addEdge(S, i, 1);
      int k;
      cin >> k;
      while (k--) {
        int x;
        cin >> x;
        x--;
        addEdge(i, x, 1);
      }
    } else if (type == 1) {
      addEdge(S, i, 1);
      int l, r;
      cin >> l >> r;
      l--, r--;
      que(l, r, i, root, 0, m - 1);
    } else {
      addEdge(S, i, 2);
      int a, b, c;
      cin >> a >> b >> c;
      a--, b--, c--;
      addEdge(i, a, 1);
      addEdge(i, b, 1);
      addEdge(i, c, 1);
      t3.push_back({a, b, c, i - m});
    }
  }
  cout << maxFlow() << "\n";
  memset(killedBy, -1, sizeof(killedBy));
  for (int i = 0; i < m; i++) {
    int x = i;
    bool alive = true;
    for (E e : g[x]) {
      if (fl[e.i] < 0) {
        alive = false;
        break;
      }
    }
    if (alive) continue;
    while (x < m || x >= n + m) {
      for (E e : g[x]) {
        if (fl[e.i] < 0) {
          fl[e.i]++;
          x = e.to;
          break;
        }
      }
    }
    killedBy[i] = x - m;
  }
  for (int i = 0; i < m; i++) {
    if (killedBy[i] != -1) {
      ct[killedBy[i]]++;
    }
  }
  for (auto x : t3) {
    if (ct[x[3]] == 1) {
      for (int i = 0; i < 3; i++) {
        if (killedBy[x[i]] != x[3]) {
          killedBy[x[i]] = x[3];
          break;
        }
      }
    }
  }
  for (int i = 0; i < m; i++) {
    if (killedBy[i] != -1) {
      cout << killedBy[i] + 1 << " " << i + 1 << "\n";
    }
  }
  return 0;
}
