#include <bits/stdc++.h>
using namespace std;
template <typename T>
inline bool chkmin(T &a, T b) {
  return b < a ? a = b, 1 : 0;
}
template <typename T>
inline bool chkmax(T &a, T b) {
  return b > a ? a = b, 1 : 0;
}
inline int read() {
  int x(0), sgn(1);
  char ch(getchar());
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') sgn = -1;
  for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
  return x * sgn;
}
void File() {}
int n, m;
const int inf = 0x3f3f3f3f;
template <int Maxn, int Maxm>
struct Dinic {
  int Head[Maxn], Next[Maxm], cap[Maxm], to[Maxm], e;
  Dinic() { e = 1; }
  inline void add_edge(int u, int v, int flow) {
    to[++e] = v;
    Next[e] = Head[u];
    Head[u] = e;
    cap[e] = flow;
  }
  inline void Add(int u, int v, int flow) {
    add_edge(u, v, flow);
    add_edge(v, u, 0);
  }
  int S, T, dis[Maxn];
  bool Bfs() {
    queue<int> Q;
    Q.push(S);
    memset(dis, 0, sizeof(dis));
    dis[S] = 1;
    while (!Q.empty()) {
      int u = Q.front();
      Q.pop();
      for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]])
        if (cap[i] && !dis[v]) dis[v] = dis[u] + 1, Q.push(v);
    }
    return dis[T];
  }
  int cur[Maxn];
  int Dfs(int u, int flow) {
    if (u == T || !flow) return flow;
    int res = 0, f;
    for (int &i = cur[u]; i; i = Next[i]) {
      int v = to[i];
      if (dis[v] == dis[u] + 1 && (f = Dfs(v, min(flow, cap[i])))) {
        cap[i] -= f, cap[i ^ 1] += f, res += f;
        if (!(flow -= f)) break;
      }
    }
    if (!res) dis[u] = 0;
    return res;
  }
  int Run() {
    int sum_flow = 0;
    while (Bfs()) memcpy(cur, Head, sizeof(cur)), sum_flow += Dfs(S, inf);
    return sum_flow;
  }
};
const int N = 5050;
Dinic<(int)1e5, (int)4e5> D;
int tot;
int ans[N], Bef;
template <int Maxn>
struct Segment_Tree {
  int id[Maxn];
  void Build(int o, int l, int r) {
    if (l == r) {
      id[o] = l;
      return;
    }
    id[o] = ++tot;
    int mid = (l + r) >> 1;
    Build(o << 1, l, mid);
    Build(o << 1 | 1, mid + 1, r);
    D.Add(id[o], id[o << 1], id[o << 1] <= m ? 1 : inf);
    D.Add(id[o], id[o << 1 | 1], id[o << 1 | 1] <= m ? 1 : inf);
  }
  inline void Connect(int o, int l, int r, int ql, int qr, int Id) {
    if (ql <= l && r <= qr) {
      D.Add(Id, id[o], 2);
      return;
    }
    int mid = (l + r) >> 1;
    if (ql <= mid) Connect(o << 1, l, mid, ql, qr, Id);
    if (qr > mid) Connect(o << 1 | 1, mid + 1, r, ql, qr, Id);
  }
  vector<int> find(int o, int l, int r) {
    if (l == r) return vector<int>();
    vector<int> tmp;
    int mid = (l + r) >> 1;
    vector<int> ls = find(o << 1, l, mid), rs = find(o << 1 | 1, mid + 1, r);
    set_union((ls).begin(), (ls).end(), (rs).begin(), (rs).end(),
              inserter(tmp, tmp.end()));
    for (int i = D.Head[id[o]], v = D.to[i]; i; v = D.to[i = D.Next[i]])
      if (v <= m && !D.cap[i]) tmp.push_back(v);
    for (int i = D.Head[id[o]], v = D.to[i]; i; v = D.to[i = D.Next[i]])
      if (D.cap[i] && v > Bef) {
        int cur = tmp[tmp.size() - 1];
        tmp.pop_back();
        ans[cur] = v - Bef;
      }
    return tmp;
  }
};
Segment_Tree<N << 2> T;
vector<int> V1, V2, V3;
int Ref[N], from[N], go[N];
void Get_Ans() {
  for (int u : V1) {
    for (int i = D.Head[Ref[u]], v = D.to[i]; i; v = D.to[i = D.Next[i]])
      if (v <= m && D.cap[i ^ 1]) ans[v] = u;
  }
  T.find(1, 1, m);
  for (int u : V3) {
    int flow = D.cap[from[u]];
    for (int i = D.Head[Ref[u]], v = D.to[i]; i; v = D.to[i = D.Next[i]])
      if (v != D.S && !D.cap[i]) ans[v] = u;
    if (flow == 1)
      for (int i = D.Head[Ref[u]], v = D.to[i]; i; v = D.to[i = D.Next[i]])
        if (v != D.S && D.cap[i]) {
          ans[v] = u;
          break;
        }
  }
}
int main() {
  File();
  n = read(), m = read();
  tot = m;
  T.Build(1, 1, m);
  Bef = tot;
  D.S = tot + n + 1;
  D.T = tot + n + 2;
  for (register int i = (1), iend = (int)(m); i <= iend; ++i)
    D.Add(i, D.T, 1), go[i] = D.e - 1;
  for (register int i = (1), iend = (int)(n); i <= iend; ++i) {
    int opt = read();
    D.Add(D.S, Ref[i] = ++tot, 1 + (opt == 2));
    from[i] = D.e - 1;
    if (opt == 0) {
      int k = read();
      V1.push_back(i);
      while (k--) D.Add(tot, read(), 1);
    } else if (opt == 1) {
      int l = read(), r = read();
      V1.push_back(i);
      T.Connect(1, 1, m, l, r, tot);
    } else {
      int a = read(), b = read(), c = read();
      V3.push_back(i);
      D.Add(tot, a, 1);
      D.Add(tot, b, 1);
      D.Add(tot, c, 1);
    }
  }
  printf("%d\n", D.Run());
  Get_Ans();
  for (register int i = (1), iend = (int)(m); i <= iend; ++i)
    if (ans[i]) printf("%d %d\n", ans[i], i);
  return 0;
}
