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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;


public class TaskD {

    public static void main(String[] arg) {
        final FastScanner in = new FastScanner(System.in);
        final PrintWriter out = new PrintWriter(System.out);
        final int n = in.nextInt();
        final Solution sol = new Solution(in, out, n);
        sol.mainLoop();
        out.flush();
        out.close();
        in.close();
    }


    private static class Solution {
        final FastScanner in;
        final PrintWriter out;
        final int n;
        final Map<Integer, Set<Integer>> g;

        public Solution(FastScanner in, PrintWriter out, int n) {
            this.in = in;
            this.out = out;
            this.n = n;
            g = new HashMap<>();
            initGraph();
        }

        private void initGraph() {
            for (int i = 1; i <= n; i++) {
                g.put(i, new HashSet<>());
            }
        }

        private void mainLoop() {
            Set<Integer> processed = new HashSet<>();
            int askNode = 1;
            while (askNode <= n && countEdges() < n - 1) {
                out.printf("? %d%n", askNode);
                out.flush();
                int[] resp = in.readIntArr(n);
                processed.add(askNode);
                for (int i = 0; i < n; i++) {
                    if (resp[i] == 1) {
                        addToGraph(askNode, i + 1);
                        processed.add(i + 1);
                    }
                }
                //traverse
                //0
                Map<Integer, Set<Integer>> distanceMap = new HashMap<>();
                for (int i = 0; i <= n; i++) {
                    distanceMap.put(i, new HashSet<>());
                }
                for (int i = 0; i < n; i++) {
                    distanceMap.get(resp[i]).add(i + 1);
                }
                int distance = 0;
                Integer node = distanceMap.get(distance).stream().findFirst().get();
                while (distanceMap.getOrDefault(distance, Collections.emptySet()).size() == 1) {
                    Set<Integer> nextNodes = distanceMap.get(distance + 1);
                    if(nextNodes==null){
                        break;
                    }
                    for (Integer n : nextNodes) {
                        addToGraph(node, n);
                        processed.add(n);
                    }
                    processed.add(node);
                    if (nextNodes.size() == 1) {
                        node = nextNodes.stream().findFirst().get();
                        processed.add(node);
                        distance++;
                    } else {
                        break;
                    }

                }
                while (processed.contains(askNode)) {
                    askNode++;
                }
            }
            out.println("!");
            out.printf("%s", graphToString());
            out.flush();
        }

        private void addToGraph(int v1, int v2) {
            if (v1 == v2) {
                return;
            }
            if(g.get(v1)==null) return;
            if(g.get(v2)==null) return;
            g.get(v1).add(v2);
            g.get(v2).add(v1);
        }

        private int countEdges() {
            int cnt = 0;
            final Set<Integer> processedVertexes = new HashSet<>();
            for (final Map.Entry<Integer, Set<Integer>> e : g.entrySet()) {
                for (Integer v1 : e.getValue()) {
                    if (processedVertexes.contains(v1)) {
                        continue;
                    }
                    cnt++;
                }
                processedVertexes.add(e.getKey());
            }
            return cnt;
        }


        private String graphToString() {
            final StringBuilder res = new StringBuilder();
            final Set<Integer> processedVertexes = new HashSet<>();
            for (final Map.Entry<Integer, Set<Integer>> e : g.entrySet()) {
                for (Integer v1 : e.getValue()) {
                    if (processedVertexes.contains(v1)) {
                        continue;
                    }
                    res.append(v1).append(' ').append(e.getKey()).append('\n');
                }
                processedVertexes.add(e.getKey());
            }
            return res.toString();
        }
    }

    private static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        FastScanner(InputStream stream) {
            try {
                br = new BufferedReader(new InputStreamReader(stream));
            } 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());
        }

        int[] readIntArr(int n) {
            int[] result = new int[n];
            for (int i = 0; i < n; i++) {
                result[i] = Integer.parseInt(next());
            }
            return result;
        }

        long[] readLongArr(int n) {
            long[] result = new long[n];
            for (int i = 0; i < n; i++) {
                result[i] = Long.parseLong(next());
            }
            return result;
        }

        void close() {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

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

    }

}