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

public class D {

	public Object solve () {
		int N = sc.nextInt();

		int [][] A = new int [N][];
		A[0] = ask(0);

		int X = 0, Y = 0;
		for (int i : rep(1, N))
			if (A[0][i] % 2 == 0)
				++X;
			else
				++Y;

		if (X <= Y) {
			for (int i : rep(1, N))
				if (A[0][i] % 2 == 0)
					A[i] = ask(i);
		} else
			for (int i : rep(1, N))
				if (A[0][i] % 2 != 0)
					A[i] = ask(i);

		boolean [][] F = new boolean [N][N];
		for (int i : rep(N))
			if (A[i] != null)
				for (int j : rep(N))
					if (A[i][j] == 1)
						F[i][j] = F[j][i] = true;

		print("!");
		for (int j : rep(N))
			for (int i : rep(j))
				if (F[i][j])
					print(i+1, j+1);

		return null;
	}

	int [] ask (int i) {
		flush("? " + (i+1));
		int [] res = sc.nextInts();
		return res;
	}

	private static final int CONTEST_TYPE = 1;

	private static void init () {
	}

	private static int [] rep (int N) { return rep(0, N); }
	private static int [] rep (int S, int T) { if (S >= T) return new int [0]; int [] res = new int [T-S]; for (int i = S; i < T; ++i) res[i-S] = i; return res; }
	//////////////////////////////////////////////////////////////////////////////////// OFF
	private static IOUtils.MyScanner sc = new IOUtils.MyScanner();
	private static void flush (Object o, Object ... A) { print(o, A); IOUtils.flush(); }
	private static Object print (Object o, Object ... A) { IOUtils.print(o, A); return null; }
	private static class IOUtils {
		public static class MyScanner {
			public String next () { newLine(); return line[index++]; }
			public int nextInt () { return Integer.parseInt(next()); }
			public String nextLine () { line = null; return readLine(); }
			public String [] nextStrings () { return split(nextLine()); }
			public int[] nextInts () { return nextStream().mapToInt(Integer::parseInt).toArray(); }
			//////////////////////////////////////////////
			private boolean eol () { return index == line.length; }
			private String readLine () {
				try {
					int b;
					StringBuilder res = new StringBuilder();
					while ((b = System.in.read()) < ' ')
						continue;
					res.append((char)b);
					while ((b = System.in.read()) >= ' ')
						res.append((char)b);
					return res.toString();
				} catch (Exception e) {
					throw new Error (e);
				}
			}
			private MyScanner () {
				try {
					while (System.in.available() == 0)
						Thread.sleep(1);
					start();
				} catch (Exception e) {
					throw new Error(e);
				}
			}
			private String [] line;
			private int index;
			private void newLine () {
				if (line == null || eol()) {
					line = split(readLine());
					index = 0;
				}
			}
			private java.util.stream.Stream<String> nextStream () { return java.util.Arrays.stream(nextStrings()); }
			private String [] split (String s) { return s.length() > 0 ? s.split(" ") : new String [0]; }
		}
		private static String build (Object o, Object ... A) { return buildDelim(" ", o, A); }
		private static String buildDelim (String delim, Object o, Object ... A) {
			java.util.List<String> str = new java.util.ArrayList<>();
			append(str, o);
			for (Object p : A)
				append(str, p);
			return String.join(delim, str.toArray(new String [0]));
		}
		//////////////////////////////////////////////////////////////////////////////////
		private static java.text.DecimalFormat formatter = new java.text.DecimalFormat("#.#########");
		private static void start () { if (t == 0) t = millis(); }
		private static void append (java.util.function.Consumer<Object> f, java.util.function.Consumer<Object> g, Object o) {
			if (o.getClass().isArray()) {
				int len = java.lang.reflect.Array.getLength(o);
				for (int i = 0; i < len; ++i)
					f.accept(java.lang.reflect.Array.get(o, i));
			}
			else if (o instanceof Iterable<?>)
				((Iterable<?>)o).forEach(f::accept);
			else
				g.accept(o instanceof Double ? formatter.format(o) : o);
		}
		private static void append (java.util.List<String> str, Object o) {
			append(x -> append(str, x), x -> str.add(x.toString()), o);
		}
		private static java.io.PrintWriter pw = new java.io.PrintWriter(System.out);
		private static void flush () { pw.flush(); System.out.flush(); }
		private static void print (Object o, Object ... A) {
			String res = build(o, A);
			if (DEBUG == 2)
				err(res, '(', time(), ')');
			if (res.length() > 0)
				pw.println(res);
			if (DEBUG == 1)
				flush();
		}
		private static void err (Object o, Object ... A) { System.err.println(build(o, A)); }
		private static int DEBUG;
		private static void exit () {
			String end = "------------------" + System.lineSeparator() + time();
			switch(DEBUG) {
			case 1: print(end); break;
			case 2: err(end); break;
			}
			IOUtils.pw.close();
			System.out.flush();
			System.exit(0);
		}
		private static long t;
		private static long millis () { return System.currentTimeMillis(); }
		private static String time () { return "Time: " + (millis() - t) / 1000.0; }
		private static void run (int N) {
			try { DEBUG = Integer.parseInt(System.getProperties().get("DEBUG").toString()); }
			catch (Throwable t) {}

			for (int n = 1; n <= N; ++n) {
				Object res = new D().solve();
				if (res != null) {
					@SuppressWarnings("all")
					Object o = CONTEST_TYPE == 0 ? "Case #" + n + ": " + build(res) : res;
					print(o);
				}
			}
			exit();
		}
	}
	////////////////////////////////////////////////////////////////////////////////////
	public static void main (String[] args) {
		@SuppressWarnings("all")
		int N = CONTEST_TYPE == 1 ? 1 : sc.nextInt();
		init();
		IOUtils.run(N);
	}
}
