#ifdef DEBUG
#  define _GLIBCXX_DEBUG
#endif
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include "testlib.h"
#define ff first
#define ss second
#define szof(x) ((int)x.size())
#ifndef LOCAL
#  define cerr __get_ce
#endif

using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef unsigned long long ull;

using namespace __gnu_pbds;
template <typename T> using ordered_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
template <typename K, typename V> using ordered_map = tree<K, V, less<K>, rb_tree_tag, tree_order_statistics_node_update>;

int const INF = (int)1e9 + 1e3;
ll const INFL = (ll)1e18 + 1e6;

class dsu {
 public:
  vector<int> p;
  int n;

  dsu(int _n) : n(_n) {
    p.resize(n);
    iota(p.begin(), p.end(), 0);
  }

  inline int get(int x) {
    return (x == p[x] ? x : (p[x] = get(p[x])));
  }

  inline bool unite(int x, int y) {
    x = get(x);
    y = get(y);
    if (x != y) {
      p[x] = y;
      return true;
    }
    return false;
  }
};

int main(int argc, char* argv[]) {
  registerGen(argc, argv, 1);

  int t = opt<int>("t");
  int minn = opt<int>("minn");
  int maxn = opt<int>("maxn");

  auto ns = rnd.partition(t, maxn / 2, minn / 2);
    
  cout << t << '\n';
  for (int q = 0; q < t; q++) {
    int n = 2 * ns[q];
    int leaves = rnd.next(1, n - 1);
    int m = int(min(int64_t(n - leaves) * (n - leaves - 1) / 2 + leaves, int64_t(2) * n));
    cout << n << " " << m << '\n';
    vector<pair<int, int>> edges;
    set<pair<int, int>> s;
    dsu d(n);
    for (int i = 0; i < n - leaves - 1; i++) {
      int x, y;
      do {
        x = rnd.next(0, n - leaves - 1);
        y = rnd.next(0, n - leaves - 1);
      } while (!d.unite(x, y));
      edges.emplace_back(x, y);
      s.emplace(min(x, y), max(x, y));
    }
    for (int i = 0; i < m - (n - leaves - 1) - leaves; i++) {
      int x, y;
      do {
        x = rnd.next(0, n - leaves - 1);
        y = rnd.next(0, n - leaves - 1);
      } while (x == y || s.find({min(x, y), max(x, y)}) != s.end());
      edges.emplace_back(x, y);
      s.emplace(min(x, y), max(x, y));
    }
    auto ls = rnd.partition(n - leaves, leaves, 0);
    int ptr = n - leaves;
    for (int i = 0; i < n - leaves; i++) {
      for (int j = 0; j < ls[i]; j++) {
        int x = i, y = ptr++;
        if (rnd.next(0, 1) == 0) swap(x, y);
        edges.emplace_back(x, y);
      }
    }
    shuffle(edges.begin(), edges.end());
    assert(int(edges.size()) == m);
    vector<int> perm(n);
    iota(perm.begin(), perm.end(), 0);
    shuffle(perm.begin(), perm.end());
    for (int i = 0; i < m; i++) {
      cout << perm[edges[i].first] + 1 << " " << perm[edges[i].second] + 1 << '\n';
    }
  }

  return 0;
}