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

//package codeforces.latokenround1;

import java.io.*;
import java.util.*;

//Think through the entire logic before jump into coding!
//Be aware of integer overflow!

public class D {
    static InputReader in;
    static PrintWriter out;

    public static void main(String[] args) {
        //initReaderPrinter(true);
        initReaderPrinter(false);
        //solve(in.nextInt());
        solve(1);
    }

    static void solve(int testCnt) {
        for (int testNumber = 0; testNumber < testCnt; testNumber++) {
            int n = in.nextInt();
            int edge = 0;
            Set<Integer>[] adj = new Set[n + 1];
            for(int i = 0; i <= n; i++) {
                adj[i] = new HashSet<>();
            }
            boolean[] processed = new boolean[n + 1];
            out.println("? 1");
            out.flush();
            int[][] d = new int[n][2];
            for(int i = 0; i < n; i++) {
                d[i][0] = in.nextInt();
                d[i][1] = i + 1;
            }
            Arrays.sort(d, Comparator.comparingInt(e->e[0]));
            processed[1] = true;
            boolean done = false;
            for(int i = n - 1; i >= 0; i--) {
                if(processed[d[i][1]]) continue;
                out.println("? " + d[i][1]);
                out.flush();
                int[][] d1 = new int[n][2];
                for(int k = 0; k < n; k++) {
                    d1[k][0] = in.nextInt();
                    d1[k][1] = k + 1;
                }
                Arrays.sort(d1, Comparator.comparingInt(e->e[0]));
                int child = d[i][1];
                for(int j = 1; j < n; j++) {
                    //current leaf node is d[i][1], its dist to node 1 is d[d[i][1]][0]; its dist to a node on
                    //the path to node 1 is d1[j][1] with dist d1[j][0]
                    if(d1[j][0] + d[d1[j][1]][0] == d[d[i][1]][0]) {
                        if(adj[d1[j][1]].add(child)) {
                            edge++;
                            if(edge == n - 1) {
                                done = true;
                                break;
                            }
                        }
                        processed[child] = true;
                        child = d1[j][1];
                    }
                }
                if(done) break;
            }
            out.println("!");
            out.flush();
            for(int i = 1; i <= n; i++) {
                for(int v : adj[i]) {
                    out.println(i + " " + v);
                    out.flush();
                }
            }
        }
        out.close();
    }

    static void initReaderPrinter(boolean test) {
        if (test) {
            try {
                in = new InputReader(new FileInputStream("src/input.in"));
                out = new PrintWriter(new FileOutputStream("src/output.out"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            in = new InputReader(System.in);
            out = new PrintWriter(System.out);
        }
    }

    static class InputReader {
        BufferedReader br;
        StringTokenizer st;

        InputReader(InputStream stream) {
            try {
                br = new BufferedReader(new InputStreamReader(stream), 32768);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

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

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }

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

        int[] nextIntArrayPrimitive(int n) {
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = nextInt();
            return a;
        }

        int[] nextIntArrayPrimitiveOneIndexed(int n) {
            int[] a = new int[n + 1];
            for (int i = 1; i <= n; i++) a[i] = nextInt();
            return a;
        }

        Long[] nextLongArray(int n) {
            Long[] a = new Long[n];
            for (int i = 0; i < n; i++) a[i] = nextLong();
            return a;
        }

        long[] nextLongArrayPrimitive(int n) {
            long[] a = new long[n];
            for (int i = 0; i < n; i++) a[i] = nextLong();
            return a;
        }

        long[] nextLongArrayPrimitiveOneIndexed(int n) {
            long[] a = new long[n + 1];
            for (int i = 1; i <= n; i++) a[i] = nextLong();
            return a;
        }

        String[] nextStringArray(int n) {
            String[] g = new String[n];
            for (int i = 0; i < n; i++) g[i] = next();
            return g;
        }
    }
}