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.*;
import java.util.*;

import static java.lang.System.out;

public class contest1534 {
    public static void main(String[] args) {
//        colourTheFlag();
//        histogramUgliness();
//        littleAlawnsPuzzle();
        lostTree();
        pw.close();
    }

    static void lostTree() {
        int n = readInt();
        HashSet<Integer>[] adj = new HashSet[n + 1];
        for (int i = 0; i <= n; i++) {
            adj[i] = new HashSet<>();
        }
        int[][] all_dist = new int[n + 1][n + 1];
        Arrays.fill(all_dist[0], Integer.MAX_VALUE);
        HashSet<Integer> queried = new HashSet<>();
        ArrayDeque<Integer> q = new ArrayDeque<>();
        q.addLast(1);
        q.addLast(0);
        StringBuilder sb = new StringBuilder();
        while (!q.isEmpty()) {
            int head = q.pollFirst();
            int ancestor = q.pollFirst();
            if (!queried.contains(head)) {
                int[] head_dist = all_dist[head];
                query(head, all_dist[head]);
                queried.add(head);
                int[] ancestor_dist = all_dist[ancestor];
                int mid = -1;
                for (int i = 1; i <= n; i++) {
                    if (head_dist[i] == 1) {
                        adj[head].add(i);
                        adj[i].add(head);
                        if (adj[ancestor].contains(i)) {
                            mid = i;
                        }
                        queried.add(i);
                        sb.append(head).append(" ").append(i).append("\n");
                    }
                }
                for (int i = 1; i <= n; i++) {
                    if (!queried.contains(i) && head_dist[i] <= ancestor_dist[i]) {
                        if (head_dist[i] == 2) {
                            if (ancestor_dist[i] == 2) {
                                queried.add(i);
                                sb.append(mid).append(" ").append(i).append("\n");
                            } else if (ancestor_dist[i] > 2) {
                                q.addLast(i);
                                q.addLast(head); // only add when not already queued?
                            }
                        } else if (head_dist[i] == 3) {
                            if (ancestor_dist[i] >= 3) {
                                q.addLast(i);
                                q.addLast(head);
                            }
                        }
                    }
                }
            }
        }
        out.println('!');
        out.println(sb);
    }

    private static void query(int r, int[] dist) {
        out.println("? " + r);
        out.flush();
        for (int i = 1; i < dist.length; i++) {
            dist[i] = readInt();
        }
    }

    static void littleAlawnsPuzzle() {
        int T = readInt();
        while (T-- > 0) {
            int n = readInt();
            int[] a = readIntArray(n);
            int[] b = readIntArray(n);
            HashMap<Integer, Integer> m = new HashMap<>();
            for (int i = 0; i < n; i++) {
                m.put(a[i], b[i]);
            }
            boolean[] visited = new boolean[n + 1];
            int cycles = 0;
            for (int i = 1; i <= n; i++) {
                if (!visited[i]) {
                    cycles += 1;
                    do {
                        visited[i] = true;
                        i = m.get(i);
                    } while (!visited[i]);
                }
            }
            pw.println(exponentiation(2, cycles, (long) 1e9 + 7));
        }
    }

    static long exponentiation(long base, int exp, long mod) {
        long power = 1;
        while (exp > 0) {
            if (exp % 2 == 1) power = power * base % mod;
            base = base * base % mod;
            exp /= 2;
        }
        return power;
    }

    static void histogramUgliness() {
        int T = readInt();
        while (T-- > 0) {
            int n = readInt();
            int[] a = readIntArray(n);
            long ans = 0;
            for (int i = 0; i < n; i++) {
                int m = Math.max(i > 0 ? a[i - 1] : 0, i < n - 1 ? a[i + 1] : 0);
                if (a[i] > m) {
                    ans += a[i] - m;
                    a[i] = m;
                }
            }
            ans += a[0] + a[n - 1];
            for (int i = 1; i < n; i++) {
                ans += Math.abs(a[i] - a[i - 1]);
            }
            pw.println(ans);
        }
    }

    static void colourTheFlag() {
        int T = readInt();
        NEXT_CASE:
        while (T-- > 0) {
            int n = readInt();
            int m = readInt();
            char[][] g = new char[n][];
            for (int i = 0; i < n; i++) {
                g[i] = readLineAsCharArray();
            }
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    int oddity = (i + j) % 2;
                    if (g[i][j] == 'R') {
                        fill(g, n, m, oddity);
                        continue NEXT_CASE;
                    } else if (g[i][j] == 'W') {
                        fill(g, n, m, 1 - oddity);
                        continue NEXT_CASE;
                    }
                }
            }
            fill(g, n, m, 0);
        }
    }

    static void fill(char[][] g, int n, int m, int oddityR) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int oddity = (i + j) % 2;
                if (oddityR == oddity) {
                    if (g[i][j] == 'W') {
                        pw.println("NO");
                        return;
                    }
                    g[i][j] = 'R';
                } else {
                    if (g[i][j] == 'R') {
                        pw.println("NO");
                        return;
                    }
                    g[i][j] = 'W';
                }
            }
        }
        pw.println("YES");
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                pw.print(g[i][j]);
            }
            pw.println();
        }
    }

    // @formatter:off
    static final BufferedReader br=new BufferedReader(new InputStreamReader(System.in));static final PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));static StringTokenizer st;static final String READ_ERROR="Error Reading.";static String readToken(){try{while(st==null||!st.hasMoreTokens())st=new StringTokenizer(br.readLine());return st.nextToken();}catch(IOException e){throw new RuntimeException(READ_ERROR);}}static int readInt(){return Integer.parseInt(readToken());}static int[]readIntArray(final int n){int[]a=new int[n];for(int i=0;i<n;i++)a[i]=readInt();return a;}static long readLong(){return Long.parseLong(readToken());}static char[]readLineAsCharArray(){try{return br.readLine().toCharArray();}catch(IOException e){throw new RuntimeException(READ_ERROR);}}static String readLine(){try{return br.readLine();}catch(IOException e){throw new RuntimeException(READ_ERROR);}}
}
