#include <bits/stdc++.h>
using namespace std;
template <class htpe, class cmp>
using heap = priority_queue<htpe, vector<htpe>, cmp>;
template <class htpe>
using min_heap = heap<htpe, greater<htpe> >;
template <class htpe>
using max_heap = heap<htpe, less<htpe> >;
const int INF = 1791791791;
const long long INFLL = 1791791791791791791ll;
const int MAX_W = 1e5;
class tree {
  struct edge {
    int u, v;
    long long w;
    edge(int _u, int _v, long long _w) : u(_u), v(_v), w(_w) {}
  };
  int n;
  vector<vector<edge> > graph;
  vector<vector<int> > good_pairs;
  vector<bool> vis;
  bool is_tree_dfs(int u, int par) {
    vis[u] = true;
    for (edge e : graph[u]) {
      int v = e.v;
      if (v != par) {
        if (vis[v]) return false;
        if (!is_tree_dfs(v, u)) return false;
      }
    }
    return true;
  }
  bool is_tree() {
    vis.assign(n, false);
    if (!is_tree_dfs(0, -1)) return false;
    for (int i = 0; i < ((int)(n)); ++i)
      if (!vis[i]) return false;
    return true;
  }
  class dsu {
    vector<int> par;
    vector<int> rank;
    vector<int> topmost_v;

   public:
    dsu(int n) {
      par.resize(n);
      iota((par).begin(), (par).end(), 0);
      rank.resize(n, 0);
      topmost_v.resize(n, 0);
      iota((topmost_v).begin(), (topmost_v).end(), 0);
    }
    int get(int v) {
      if (v != par[v]) par[v] = get(par[v]);
      return par[v];
    }
    int topmost(int v) { return topmost_v[get(v)]; }
    void merge(int u, int v, int tm) {
      u = get(u);
      v = get(v);
      if (u == v) return;
      if (rank[u] > rank[v]) swap(u, v);
      par[u] = v;
      rank[v] = max(rank[v], rank[u] + 1);
      topmost_v[v] = tm;
    }
  };
  vector<long long> upsum;
  bool are_sums_correct_dfs(int u, int par, long long the_upsum,
                            const vector<long long> &ps, dsu &ds) {
    upsum[u] = the_upsum;
    vis[u] = true;
    for (int v : good_pairs[u]) {
      if (vis[v]) {
        int lca = ds.topmost(v);
        long long dist = upsum[u] + upsum[v] - 2 * upsum[lca];
        int l = u, r = v;
        if (l > r) swap(l, r);
        if (dist != ps[r + 1] - ps[l]) return false;
      }
    }
    for (edge e : graph[u]) {
      int v = e.v;
      if (v != par) {
        if (!are_sums_correct_dfs(v, u, the_upsum + e.w, ps, ds)) return false;
        ds.merge(u, v, u);
      }
    }
    return true;
  }
  bool are_sums_correct() {
    vis.assign(n, false);
    upsum.assign(n, 0);
    vector<long long> pre_v(n + 1);
    for (int u = 0; u < ((int)(n)); ++u) {
      for (edge e : graph[u]) {
        if (u < e.v) {
          pre_v[u] += e.w;
          pre_v[e.v + 1] -= e.w;
        }
      }
    }
    partial_sum((pre_v).begin(), (pre_v).end(), pre_v.begin());
    vector<long long> ps = {0};
    partial_sum((pre_v).begin(), (pre_v).end(), back_inserter(ps));
    dsu ds(n);
    return are_sums_correct_dfs(0, -1, 0, ps, ds);
  }
  int ngp = 0;

 public:
  tree(int _n) {
    n = _n;
    graph.resize(n);
    good_pairs.resize(n);
  }
  void add_edge(int u, int v, int w) {
    assert(1 <= w && w <= MAX_W);
    graph[u].push_back(edge(u, v, w));
    graph[v].push_back(edge(v, u, w));
  }
  void add_good_pair(int u, int v) {
    good_pairs[u].push_back(v);
    good_pairs[v].push_back(u);
    ngp++;
  }
  void verify() {
    assert(is_tree());
    assert(are_sums_correct());
    assert(ngp == n / 2);
  }
  friend ostream &operator<<(ostream &, const tree &);
};
ostream &operator<<(ostream &os, const tree &tr) {
  for (int u = 0; u < ((int)(tr.n)); ++u) {
    for (tree::edge e : tr.graph[u]) {
      if (u < e.v) {
        os << u + 1 << " " << e.v + 1 << " " << e.w << endl;
      }
    }
  }
  for (int u = 0; u < ((int)(tr.n)); ++u) {
    for (int v : tr.good_pairs[u]) {
      if (u < v) {
        os << u + 1 << " " << v + 1 << endl;
      }
    }
  }
  return os;
}
int main() {
  int n;
  cin >> n;
  tree tr(n);
  if (n == 5) {
    tr.add_edge(0, 3, 1);
    tr.add_edge(1, 2, 2);
    tr.add_edge(1, 4, 1);
    tr.add_edge(2, 3, 2);
    tr.add_good_pair(0, 1);
    tr.add_good_pair(3, 4);
  } else {
    int b = 0;
    if (n % 2 == 1) {
      b = 1;
      n--;
      tr.add_edge(0, 1, 1);
    }
    for (int i = 0; i < ((int)((n / 2) - 1)); ++i) {
      tr.add_edge(b + i, b + i + 1, 2 * i + 1);
      tr.add_edge(b + i, b + (n - i - 1), 1);
    }
    tr.add_edge(b + (n / 2) - 1, b + (n / 2), 1);
    for (int i = (int)((n / 2)); i < ((int)(n - 1)); ++i)
      tr.add_good_pair(b + i, b + i + 1);
    tr.add_good_pair(b + n - 3, b + n - 1);
  }
  tr.verify();
  cout << tr;
  return 0;
}
