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;
template <typename T1, typename T2>
ostream &operator<<(ostream &os, const pair<T1, T2> p) {
  os << '(' << p.first << ',' << p.second << ')';
  return os;
}
template <typename T>
ostream &operator<<(ostream &os, const vector<T> p) {
  os << "{ ";
  for (auto i : p) {
    os << i << ",";
  }
  os << "\b}";
  return os;
}
template <typename T1, typename T2>
ostream &operator<<(ostream &os, const map<T1, T2> p) {
  os << "{";
  bool f = false;
  for (auto i : p) {
    os << i.first << ":" << i.second << ", ";
    f = true;
  }
  if (f) {
    os << "\b\b";
  }
  os << "}";
  return os;
}
template <typename T1, typename T2>
istream &operator>>(istream &in, pair<T1, T2> &p) {
  in >> p.first >> p.second;
  return in;
}
namespace mlk {
class Performance {
 private:
  static map<string, chrono::time_point<chrono::high_resolution_clock>> mapping;

 public:
  static void time(string fnName) {
    mapping[fnName] = chrono::high_resolution_clock::now();
  }
  static void timeEnd(string fnName) {
    assert(mapping.find(fnName) != mapping.end());
    auto end = chrono::high_resolution_clock::now();
    cerr << fnName << ": "
         << chrono::duration_cast<chrono::milliseconds>(end - mapping[fnName])
                .count()
         << " miliseconds - timer ended\n";
    mapping.erase(fnName);
  }
  static void timeLog(string fnName) {
    assert(mapping.find(fnName) != mapping.end());
    auto end = chrono::high_resolution_clock::now();
    cerr << fnName << ": "
         << chrono::duration_cast<chrono::milliseconds>(end - mapping[fnName])
                .count()
         << " miliseconds\n";
  }
};
map<string, chrono::time_point<chrono::high_resolution_clock>>
    Performance::mapping;
class Primes {
 private:
  static vector<int> _primes;
  static int _n;

 public:
  static void compute(int n) {
    _primes.resize(n + 1);
    _n = n + 1;
    _primes[0] = 1;
    for (int i = 1; i < _n; i++) {
      _primes[i] = i;
    }
    for (int i = 2; i < _n; i++) {
      if (_primes[i] == i) {
        for (int j = 2 * i; j < _n; j += i) {
          _primes[j] = i;
        }
      }
    }
  }
  static bool isPrime(int n) {
    if (_primes[n] == n) {
      return true;
    }
    return false;
  }
  static int lpf(int n) { return _primes[n]; }
};
vector<int> Primes::_primes;
int Primes::_n;
long long power(long long base, long long exponent) {
  long long ans = 1;
  while (exponent > 0) {
    if (exponent & 1ll) ans = (ans * base);
    exponent >>= 1ll;
    base = base * base;
  }
  return ans;
}
long long power(long long base, long long exponent, long long MOD) {
  long long ans = 1ll;
  while (exponent > 0) {
    if (exponent & 1ll) ans = (ans * base) % MOD;
    exponent >>= 1ll;
    base = (base * base) % MOD;
  }
  return ans;
}
int modInverse(long long n, long long p) { return power(n, p - 2ll, p); }
class Factorials {
 private:
  static vector<long long> _factorials;
  static int _n;
  static vector<long long> _mod_inv_cache;

 public:
  static bool useCache;
  static void compute(int n, long long MOD = 1e9 + 7) {
    _n = n + 1;
    _factorials.resize(_n);
    _factorials[0] = 1;
    for (int i = 1; i < _n; i++) {
      _factorials[i] = (1ll * i * _factorials[i - 1]) % MOD;
    }
    if (useCache) {
      _mod_inv_cache.resize(_n);
      _mod_inv_cache[0] = 1;
      for (int i = 1; i < _n; i++) {
        _mod_inv_cache[i] = modInverse(_factorials[i], MOD);
      }
    }
  }
  static long long factorial(int idx) { return _factorials[idx]; }
  static long long ncr(long long n, long long r, long long MOD = 1e9 + 7) {
    if (r == 0) {
      return 1;
    }
    if (!useCache) {
      return (1ll * _factorials[n] * modInverse(_factorials[r], MOD) % MOD *
              modInverse(_factorials[n - r], MOD) % MOD) %
             MOD;
    }
    return (((1ll * _factorials[n] * _mod_inv_cache[r]) % MOD) *
            _mod_inv_cache[n - r]) %
           MOD;
  }
};
vector<long long> Factorials::_factorials;
int Factorials::_n;
bool Factorials::useCache = false;
vector<long long> Factorials::_mod_inv_cache;
template <typename T>
void keySort(vector<T> &arr, vector<int> &key) {
  int n = arr.size();
  if (key.size() != n) {
    throw runtime_error("Size of data array not equals to size of key array");
  }
  vector<T> ans(n);
  int ar[n];
  for (int i = 0; i < n; i++) {
    ar[key[i]] = i;
  }
  for (int i = 0; i < n; i++) {
    ans[i] = arr[ar[i]];
  }
  arr = ans;
}
}  // namespace mlk
using namespace mlk;
void solve(int);
int main() {
  int i = 0;
  solve(i);
  return 0;
}
vector<vector<int>> distances;
int qty = 0;
vector<int> ask(int n) {
  qty--;
  cout << "? " << n + 1 << endl;
  cout.flush();
  vector<int> vc(distances[0].size());
  for (auto &i : vc) {
    cin >> i;
  }
  return vc;
}
void answer(vector<pair<int, int>> &vc) {
  cout << "!\n";
  for (auto i : vc) {
    cout << i.first << ' ' << i.second << endl;
  }
  cout.flush();
  return;
}
void resolveParent(int gp, int child) {
  int n = distances[gp].size();
  vector<int> ans(n);
  int par = 0;
  for (int i = 0; i < n; i++) {
    if (distances[child][i] == distances[gp][i]) {
      if (distances[child][i] == 1) {
        par = i;
      }
      ans[i] = distances[child][i] - 1;
    } else {
      ans[i] = (distances[child][i] + distances[gp][i]) / 2;
    }
  }
  distances[par] = ans;
}
void resolveSibling(int gp, int sibling, int cur) {
  int n = distances[gp].size();
  int parent = 0;
  for (int i = 0; i < n; i++) {
    if (distances[gp][i] == 1 && distances[sibling][i] == 1) {
      parent = i;
      break;
    }
  }
  for (int i = 0; i < n; i++) {
    distances[cur][i] = distances[parent][i] + 1;
  }
  distances[cur][cur] = 0;
}
void solveGC(int gp) {
  vector<int> q;
  int n = distances[0].size();
  int lastSib = -1;
  for (int i = 0; i < n; i++) {
    if (distances[gp][i] == 2 && distances[i][0] == -1) {
      lastSib = i;
      if (qty != 0) {
        distances[i] = ask(i);
        resolveParent(gp, i);
      } else {
        resolveSibling(gp, lastSib, i);
      }
      q.push_back(i);
    }
  }
  for (auto i : q) {
    solveGC(i);
  }
}
void solve(int) {
  int n;
  cin >> n;
  qty = ceil(n / 2.0);
  distances = vector<vector<int>>(n, vector<int>(n, -1));
  auto gp = ask(0);
  distances[0] = gp;
  solveGC(0);
  vector<pair<int, int>> ans;
  ans.reserve(n - 1);
  for (int i = 0; i < n; i++) {
    for (int j = i + 1; j < n; j++) {
      if (distances[i][j] == 1) {
        ans.push_back({i + 1, j + 1});
      }
    }
  }
  answer(ans);
}
