/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author aajisaka
 */

#include<bits/stdc++.h>

using namespace std;

void debug_out() { cerr << endl; }
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
  cerr << " " << to_string(H);
  debug_out(T...);
}
#ifdef LOCAL
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

#define SPEED ios_base::sync_with_stdio(false);cin.tie(nullptr)
#define rep(i,n) for(int i=0; i<(int)(n); i++)
#define all(v) v.begin(), v.end()
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return true; } return false; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return true; } return false; }

using ll = long long;
using ull = unsigned long long;
using P = pair<ll, ll>;

constexpr long double PI = 3.14159265358979323846264338327950288L;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
struct dinic {
  struct edge { int to; ll cost; int rev; };
  int N;
  vector<vector<edge>> graph;
  vector<int> level;
  vector<int> iter;

  void init(int n) {
    graph.resize(n+1, vector<edge>());
    level.resize(n+1);
    iter.resize(n+1);
    N=n+1;
  }

  void add_edge(int from, int to, ll cost) {
    int r1 = graph[from].size();
    int r2 = graph[to].size();
    graph[from].push_back((edge){to, cost, r2});
    graph[to].push_back((edge){from, 0, r1});
  }

  void bfs(int s) {
    for(int i=0; i<N; i++) level[i] = -1;
    queue<int> que;
    level[s] = 0; que.push(s);
    while(!que.empty()) {
      int v = que.front(); que.pop();
      for(auto& e: graph[v]) {
        if (e.cost > 0 && level[e.to] < 0) {
          level[e.to] = level[v] + 1;
          que.push(e.to);
        }
      }
    }
  }

  ll dfs(int v, int t, ll f) {
    if (v == t) return f;
    for(int &i = iter[v]; i < graph[v].size(); i++) {
      iter[v] = i;
      edge &e = graph[v][i];
      if (e.cost > 0 && level[v] < level[e.to]) {
        ll d = dfs(e.to, t, min(f, e.cost));
        if (d > 0) {
          e.cost -= d;
          graph[e.to][e.rev].cost += d;
          return d;
        }
      }
    }
    return 0;
  }

  ll max_flow(int s, int t) {
    ll flow = 0;
    while(true) {
      bfs(s);
      if (level[t] < 0) return flow;
      for(int i=0; i<N; i++) iter[i] = 0;
      ll f;
      while((f = dfs(s, t, LLONG_MAX/4)) > 0) {
        flow += f;
      }
    }
  }
};

class FDistributeNumbers {
public:
    void solve(istream& cin, ostream& cout) {
      SPEED;
      int k = 38;
      int n = 1407;
      //int k = 4;
      //int n = 13;

      int p = k-1;
      cout << n << ' ' << k << endl;

      vector<int> ret[n];
      for(int i=0; i<n-1; i++) {
        ret[i/p].push_back(i);
      }
      for(int i=0; i<p+1; i++) {
        ret[i].push_back(n-1);
      }
      int idx = p+1;

      for(int d=0; d<p; d++) {
        int m = d;
        for(int i=0; i<p; i++) {
          for (int j = 0; j < p; j++) {
            ret[idx].push_back(j*p+m);
            m += i;
            m %= p;
          }
          ret[idx].push_back(n-2-i);
          idx++;
        }
      }

      rep(i, n) {
        for(auto e: ret[i]) {
          cout << e+1 << ' ';
        }
        cout << '\n';
      }
    }
};

signed main() {
  FDistributeNumbers solver;
  std::istream& in(std::cin);
  std::ostream& out(std::cout);
  solver.solve(in, out);
  return 0;
}

Measured memory_bytes: 4004