5
5 3
2 4
3 1
4 5
 4
2 4
3 2
2 1
 5
5 3
2 4
3 1
4 5
 2
1 2
 4
3 4
2 3
3 1
 10
8 10
2 9
7 8
4 1
7 3
4 7
2 5
2 4
4 6
 5
3 4
3 2
3 1
1 5
 3
1 2
2 3
 5
1 2
1 3
1 4
1 5

1 2
2 3
2 4
 1 3
2 4
3 5
4 5
 1 2
 1 3
2 3
3 4
 1 4
2 4
2 5
2 9
3 7
4 6
4 7
7 8
8 10
 1 3
1 5
2 3
3 4
 1 2
2 3
 1 2
1 3
1 4
1 5

#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> dist;
int n;
vector<int> par;
void query(int v) {
  if (dist[v][v] != -1) return;
  printf("? %d\n", v + 1);
  fflush(stdout);
  for (int i = 0; i < n; ++i) {
    scanf("%d", &dist[v][i]);
    dist[i][v] = dist[v][i];
  }
}
void reconstruct(int root) {
  vector<int> vertices(n);
  iota((vertices).begin(), (vertices).end(), 0);
  sort((vertices).begin(), (vertices).end(),
       [&](int i, int j) { return dist[root][i] > dist[root][j]; });
  for (int leaf : vertices) {
    if (par[leaf] != -1 || leaf == root) continue;
    if (dist[root][leaf] == 1) {
      par[leaf] = root;
      continue;
    }
    query(leaf);
    int v = leaf;
    while (true) {
      if (v == root) break;
      if (par[v] != -1) break;
      for (int u = 0; u < n; ++u) {
        if (dist[root][v] + 1 == dist[root][u] &&
            dist[leaf][v] + 1 == dist[leaf][u]) {
          par[u] = v;
        }
      }
      int p = -1;
      for (int u = 0; u < n; ++u) {
        if (dist[root][u] + 1 == dist[root][v] &&
            dist[leaf][v] + 1 == dist[leaf][u]) {
          p = u;
          break;
        }
      }
      assert(p != -1);
      par[v] = p;
      v = p;
    }
  }
  printf("!\n");
  for (int i = 0; i < n; ++i) {
    int p = par[i];
    if (p != -1) printf("%d %d\n", p + 1, i + 1);
  }
  fflush(stdout);
}
void solve() {
  scanf("%d", &n);
  dist.assign(n, vector<int>(n, -1));
  par.assign(n, -1);
  query(0);
  reconstruct(0);
}
int main() {
  solve();
  return 0;
}
