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

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Set;
import java.util.HashMap;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.TreeSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.Map;
import java.io.BufferedReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in Actual solution is at the top
 *
 * @author Pradyumn Agrawal (coderbond007)
 */
public class Main {
  public static void main(String[] args) {
    InputStream inputStream = System.in;
    OutputStream outputStream = System.out;
    FastScanner in = new FastScanner(inputStream);
    PrintWriter out = new PrintWriter(outputStream);
    DLostTree solver = new DLostTree();
    solver.solve(1, in, out);
    out.close();
  }

  static class DLostTree {
    public void solve(int testNumber, FastScanner in, PrintWriter out) {
      int n = in.nextInt();
      Set<Integer>[] g = new Set[n];
      for (int i = 0; i < n; ++i) g[i] = new HashSet<>();
      Map<Integer, Integer> grandParent = new HashMap<>();
      Map<Integer, int[]> store = new HashMap<>();
      Set<Integer> visited = new HashSet<>();
      TreeSet<Integer> set = new TreeSet<>();
      set.add(0);
      grandParent.put(0, -1);
      int c = (n + 1) / 2;
      while (!set.isEmpty()) {
        if (c == 0) break;
        int cur = set.pollFirst();
        if (visited.contains(cur)) {
          continue;
        }
        int[] ans = get(in, out, cur, n);
        --c;
        store.put(cur, ans);
        visited.add(cur);

        boolean haveSameChild = false;
        int papa = grandParent.get(cur);
        Set<Integer> _2dist = new HashSet<>();

        // just child completed
        for (int i = 0; i < n; ++i) {
          if (ans[i] == 1) {
            g[cur].add(i);
            g[i].add(cur);
          } else if (ans[i] == 2) {
            if (papa != -1) {
              int[] old = store.get(papa);
              if (old[i] == ans[i]) {
                _2dist.add(i);
                haveSameChild = true;
              }
            }
          }
        }

        if (papa != -1 && haveSameChild) {
          int[] old = store.get(papa);
          // next neighbours
          for (Integer next : g[cur]) {
            if (g[next].contains(papa)) {
              for (Integer _2nd : _2dist) {
                if (old[_2nd] == ans[_2nd]) {
                  g[next].add(_2nd);
                  g[_2nd].add(next);
                }
              }
              break;
            }
          }
        }

        for (int i = 0; i < n; ++i) {
          if (ans[i] == 2 && i != papa) {
            grandParent.put(i, cur);
            set.add(i);
          }
        }
      }

      Set<Pair<Integer, Integer>> edges = new HashSet<>();
      for (int i = 0; i < n; ++i) {
        for (Integer next : g[i]) {
          edges.add(new Pair<>(Math.min(i, next), Math.max(i, next)));
        }
      }
      if (edges.size() != n - 1) {
        throw new RuntimeException("failed");
      }
      out.println("!");
      for (Pair<Integer, Integer> edge : edges) {
        out.println((edge.first + 1) + " " + (edge.second + 1));
      }
      out.flush();
    }

    public int[] get(FastScanner in, PrintWriter out, int cur, int n) {
      out.println("? " + (cur + 1));
      out.flush();
      return in.nextIntArray(n);
    }
  }

  static class FastScanner {
    BufferedReader reader;
    StringTokenizer tokenizer;

    public FastScanner(InputStream inputStream) {
      reader = new BufferedReader(new InputStreamReader(inputStream), 32768);
      tokenizer = null;
    }

    public String next() {
      while (tokenizer == null || !tokenizer.hasMoreTokens()) {
        try {
          tokenizer = new StringTokenizer(reader.readLine());
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
      return tokenizer.nextToken();
    }

    public int[] nextIntArray(int n) {
      int[] arr = new int[n];
      for (int i = 0; i < n; i++) {
        arr[i] = nextInt();
      }
      return arr;
    }

    public int nextInt() {
      return Integer.parseInt(next());
    }
  }

  static class Pair<U, V> implements Comparable<Pair<U, V>> {
    public final U first;
    public final V second;

    public Pair(U first, V second) {
      this.first = first;
      this.second = second;
    }

    public int hashCode() {
      int result = first != null ? first.hashCode() : 0;
      result = 31 * result + (second != null ? second.hashCode() : 0);
      return result;
    }

    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }

      Pair pair = (Pair) o;

      return Objects.equals(first, pair.first) && Objects.equals(second, pair.second);
    }

    public String toString() {
      return "(" + first + "," + second + ")";
    }

    public int compareTo(Pair<U, V> o) {
      int value = ((Comparable<U>) first).compareTo(o.first);
      if (value != 0) {
        return value;
      }
      return ((Comparable<V>) second).compareTo(o.second);
    }
  }
}
