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.ArrayList;
import java.util.LinkedList;
import java.util.Random;
import java.util.TreeSet;

public class DLATOKEN1 {
    public static void main(String[] args) throws IOException {
        init_io();
        int N = nint();
        boolean[] visited = new boolean[N+1];
        boolean[][] alreadyadj = new boolean[N+1][N+1];
        ArrayList<Edge> edges = new ArrayList<>();
        TreeSet<Integer> q = new TreeSet<>();
        q.add(1);
        while(q.size() > 0) {
            if (edges.size() == N-1) break;
            int c = q.pollFirst();
            if (visited[c]) continue;
            visited[c] = true;
            System.out.printf("? %d\n", c);
            System.out.flush();
            int[] arr = new int[N+1];
            int curdist = 1;
            for (int i = 1; i <= N; i++) {
                arr[i] = nint();
                if (arr[i] == 1) {
                    if (!alreadyadj[c][i]) {
                        edges.add(new Edge(c, i));
                        alreadyadj[c][i] = alreadyadj[i][c] = true;
                    }
                }
            }
            int cnt1 = 0, cnt2 = 0;
            for (int i = 1; i <= N; i++) {
                if (visited[i]) continue;
                if (arr[i] == 1 || arr[i] == 3) cnt1++;
                if (arr[i] == 2) cnt2++;
            }
            if (cnt2 > cnt1) {
                for (int i = 1; i <= N; i++) {
                    if (!visited[i] && arr[i] == 1) {
                        q.add(i);
                    }
                }
            }
            else {
                for (int i = 1; i <= N; i++) {
                    if (!visited[i] && arr[i] == 2) {
                        q.add(i);
                    }
                }
            }
        }
        System.out.println("!");
        for (Edge e : edges) {
            System.out.printf("%d %d\n", e.a, e.b);
        }
//        boolean[][] alreadyadj = new boolean[N+1][N+1];
//        ArrayList<Integer> order = new ArrayList<>();
//        for (int i = 1; i <= N; i++) order.add(i);
//        shuffle(order);
//        for (int k = 0; k < N; k++) {
//            if (edges.size() == N-1) break;
//            int i = order.get(k);
//            if (seen[i]) continue;
//            seen[i] = true;
//            System.out.printf("? %d\n", i);
//            System.out.flush();
//            int[] arr = new int[N+1];
//            ArrayList<Integer> adj = new ArrayList<>();
//            for (int j = 1; j <= N; j++) {
//                arr[j] = nint();
//                if (arr[j] == 1 && !alreadyadj[i][j]) {
//                    alreadyadj[i][j] = alreadyadj[j][i] = true;
//                    adj.add(j);
//                    edges.add(new Edge(i, j));
//                }
//            }
//            int curval = 2;
//            while (adj.size() == 1) {
//                int par = adj.get(0);
//                seen[par] = true;
//                adj.clear();
//                for (int j = 1; j <= N; j++) {
//                    if (arr[j] == curval) {
//                        adj.add(j);
//                        if (!alreadyadj[par][j]) {
//                            alreadyadj[par][j] = alreadyadj[j][par] = true;
//                            edges.add(new Edge(par, j));
//                        }
//                    }
//                }
//                curval++;
//            }
//        }
//        System.out.println("!");
//        for (Edge e : edges) {
//            System.out.printf("%d %d\n", e.a, e.b);
//        }
    }
//    public static void shuffle(ArrayList<Integer> arr) {
//        long seed = System.nanoTime();
//        Random r = new Random(seed);
//        for (int i = 0; i < 10000; i++) {
//            int a = r.nextInt(arr.size()), b = r.nextInt(arr.size());
//            int vala = arr.get(a), valb = arr.get(b);
//            arr.set(a, valb); arr.set(b, vala);
//        }
//    }
    static class Edge {
        int a, b;
        public Edge(int a1, int b1) {
            a = a1; b = b1;
        }
    }
    static StreamTokenizer in;
    static PrintWriter out;
    static BufferedReader br;

    static int nint() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }

    static void init_io() throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        in = new StreamTokenizer(br);
    }
}