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;


// ordered statistcs tree
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> indexed_set;


#define FOR(i, m, n) for(int i = m; i < n; ++i)
#define mod 1000000007
#define ll long long

int n;
int adj_matrix[2001][2001];
vector<int> adj_list[2001][2]; //nodes 1 and 2 away
bool visited[2001];
bool queried[2001][2001];
int edges = 0;



int main(){
  cin >> n;
  queue<vector<int> > q; //current node, previous node
  q.push({1, -1});
  while(!q.empty() && edges < n-1){
    vector<int> curr = q.front();
    q.pop();
    visited[curr[0]] = true;
    if (queried[curr[0]][curr[1]] == true){
      continue;
    }
    cout << "? " << curr[0] << "\n";
    cout.flush();
    int common_neighbour;
    FOR(i, 1, n + 1){ //figure out distances
      int d;
      cin >> d;
      if (d == 1){
        adj_list[curr[0]][0].push_back(i);
        adj_matrix[curr[0]][i] = 1;
        adj_matrix[i][curr[0]] = 1;
        edges++;
        if (curr[1] != -1){
          if (adj_matrix[i][curr[1]] == 1){
            common_neighbour = i;
          }
        }
      }
      else if (d == 2){
        adj_list[curr[0]][1].push_back(i);
        adj_matrix[curr[0]][i] = 2;
        adj_matrix[i][curr[0]] = 2;
      }
    }
    if (curr[1] != -1){
      for (int next : adj_list[curr[0]][1]){
        if (adj_matrix[curr[1]][next] == 2){
          adj_list[common_neighbour][0].push_back(next);
          adj_matrix[common_neighbour][next] = 1;
          adj_matrix[next][common_neighbour] = 1;
          edges++;
          queried[curr[1]][next] = true;
          queried[next][curr[1]] = true;
        }
      }
    }
    for (int next : adj_list[curr[0]][1]){
      if(queried[curr[0]][next] == false && visited[next] == false){
        //visited[next] = true;
        //queried[curr[0]][next] = true;
        //queried[next][curr[0]] = true;
        q.push({next, curr[0]});

      }
    }

    /*if (curr[1] != -1){
      for (int snext : adj_list[curr[0]][1]){ //for every node 2 away from current, if it is 2 away from prev, it is an edge of common neighbour
        if (adj_matrix[snext][curr[1]] == 2){
          adj_list[curr[2]][0].push_back(snext);
          edges++;
          adj_matrix[curr[2]][snext] = 1;
          adj_matrix[snext][curr[2]] = 1; 
        }
      }
    }*/

    // now for every unvisted neighbour
    /*for (int next : adj_list[curr[0]][0]){
      if (visited[next] == false){
        int query_node = finduvnode(next); // find neigbour of next that is unvisited so we can query
        if (query_node != -1){ // if -1 then it is a leaf
          visited[query_node] = true;
          q.push({query_node, curr[0], next});
        }
      }
    }*/

  }

  cout << "!\n";
  FOR(i, 1, n+1){
    FOR(j, i + 1, n+1){
      if (adj_matrix[i][j] == 1){
        cout << i << " " <<  j << "\n";
      }
    }
  }
 
  
}