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.Double.parseDouble;
import static java.lang.Integer.compare;
import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.lang.System.in;
import static java.lang.System.out;
import static java.util.Arrays.asList;
import static java.util.Collections.max;
import static java.util.Collections.min;

public class Main {
    private static final int MOD = (int) (1E9 + 7);
    private static final int INF = (int) (1E9);
    static FastScanner scanner = new FastScanner(in);

    public static void main(String[] args) throws IOException {
        // Write your solution here
        StringBuilder answer = new StringBuilder();
        int t = 1;
//        t = parseInt(scanner.nextLine());
        while (t-- > 0) {
            answer.append(solve()).append("\n");
        }
//        out.println(answer);
    }

    private static Object solve() throws IOException {
        int n = scanner.nextInt();
        boolean[] used = new boolean[n + 1];
        List<int[]> ans = new ArrayList<>();
        List<int[]> tree = new ArrayList<>();
        int root = 1;
        for (int iii = 0; iii < Math.ceil(n / 2d); iii++) {
            if (used[root]){
                root = -1;
                for (int i = 1; i < n + 1; i++) {
                    if(!used[i]) root = i;
                }
                if(root == -1) break;
            }
            used[root] = true;
            out.println("? " + root);
            System.out.flush();
            int[] d = new int[n];
            int min = n + 1;
            int idx = 1;
            for (int i = 0; i < n; i++) {
                d[i] = scanner.nextInt();
                if (d[i] == 1) {
                    ans.add(new int[]{root, i + 1});
                    used[i+1] = true;
                } else if (min > d[i] && !used[i + 1]) {
                    min = d[i];
                    idx = i+1;
                }
            }
            tree.add(d);
            root = idx;

        }
        for (int i = 1; i < n + 1; i++) {
            if(!used[i]){
                HashSet<Integer> set = new HashSet<>();
                int[] d = tree.get(0);
                for (int j = 0; j < n; j++) {
                    if(d[j] == d[i-1] - 1) set.add(j);
                }
                for (int j = 1; j < tree.size() && set.size() > 1; j++) {
                    int[] dist = tree.get(1);
                    HashSet<Integer> temp = new HashSet<>();
                    for (int k = 0; k < n; k++) {
                        if(dist[k] == dist[i-1] - 1 && set.contains(k)) temp.add(k);
                    }
                    set = temp;
                }
                for(int ele : set){
                    ans.add(new int[]{i, ele + 1});
                }
            }
        }
        out.println("!");
        for(int[] e : ans){
            out.println(e[0] + " " + e[1]);
        }
        return "";
    }

    private static int maxx(Integer... a) {
        return max(asList(a));
    }

    private static int minn(Integer... a) {
        return min(asList(a));
    }

    private static long maxx(Long... a) {
        return max(asList(a));
    }

    private static long minn(Long... a) {
        return min(asList(a));
    }

    private static int add(int a, int b) {
        long res = ((long) (a + MOD) % MOD + (b + MOD) % MOD) % MOD;
        return (int) res;
    }

    private static int mul(int a, int b) {
        long res = ((long) a % MOD * b % MOD) % MOD;
        return (int) res;
    }

    private static int pow(int a, int b) {
        a %= MOD;
        int res = 1;
        while (b > 0) {
            if ((b & 1) != 0)
                res = mul(res, a);
            a = mul(a, a);
            b >>= 1;
        }
        return res;
    }

    private static int gcd(int a, int b) {
        if (b == 0)
            return a;
        return gcd(b, a % b);
    }

    private static int lcm(int a, int b) {
        return (a / gcd(a, b)) * b;
    }

    private static class Pair implements Comparable<Pair> {
        int index, value;

        public Pair(int index, int value) {
            this.index = index;
            this.value = value;
        }

        public int compareTo(Pair o) {
            if (value != o.value) return compare(value, o.value);
            return compare(index, o.index);
        }
    }

    static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        FastScanner(File f) {
            try {
                br = new BufferedReader(new FileReader(f));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        FastScanner(InputStream f) {
            br = new BufferedReader(new InputStreamReader(f));
        }

        String nextLine() {
            try {
                return br.readLine();
            } catch (IOException e) {
                return null;
            }
        }

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

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

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

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

    }
}