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.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Objects;
import java.util.StringTokenizer;
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<Pair<Integer, Integer>> edges = new HashSet<>();
      int[] cnt = new int[2];
      int[] dist = get(in, out, 0, n, edges);
      for (int i = 0; i < n; ++i) {
        ++cnt[dist[i] % 2];
      }
      int oddity = cnt[0] > cnt[1] ? 1 : 0;
      for (int i = 1; i < n; ++i) {
        if (dist[i] % 2 == oddity) {
          get(in, out, i, n, edges);
        }
      }

      out.println("!");
      for (Pair<Integer, Integer> curEdge : edges) {
        out.println(curEdge.first + " " + curEdge.second);
      }
      out.flush();
    }

    public int[] get(
        FastScanner in, PrintWriter out, int cur, int n, Set<Pair<Integer, Integer>> edges) {
      out.println("? " + (cur + 1));
      out.flush();
      int[] dist = in.nextIntArray(n);
      for (int i = 0; i < n; ++i) {
        if (dist[i] == 1) {
          edges.add(new Pair<>(Math.min(cur + 1, i + 1), Math.max(cur + 1, i + 1)));
        }
      }
      return dist;
    }
  }

  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);
    }
  }

  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());
    }
  }
}
