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 java.math.*;
import java.math.BigInteger; 
//import javafx.util.*; 
public final class B
{    
	static PrintWriter out = new PrintWriter(System.out); 
	static StringBuilder ans=new StringBuilder();
	static FastReader in=new FastReader();
	static ArrayList<Integer> g[],og[];
	static long mod=(long)(1e9+7);
	static int D1[],D2[],par[];
	static boolean set[];
	static int value[];
	static long INF=Long.MAX_VALUE;
	static int N,M;
	static int val[],tot[],max[];
	static int min;
	static int dp1[][],dp2[][],path1[][],path2[][],two[][],five[][];
	static int path[][],A[];
	public static void main(String args[])throws IOException
	{
		int N=i();
		int A[]=ask(1,N);
		int D[]=new int[N+5];
		int dp[][]=new int[2][N+5];
		int par[]=new int[N+5];
		boolean edge[][]=new boolean[N+5][N+5];
		HashMap<Integer,ArrayList<Integer>> mp=new HashMap<>();
		for(int i=0; i<N; i++)
		{
			if(A[i]==1)
			{
				edge[1][i+1]=true;
				edge[i+1][1]=true;
			}
			D[A[i]]++;
			ArrayList<Integer> X=mp.getOrDefault(A[i], new ArrayList<Integer>());
			X.add(i+1);
			mp.put(A[i], X);
		}
		ArrayList<Integer> B=new ArrayList<>();
		f(dp,0,D,par);
		int d=par[0];
		while(D[d]>0)
		{
			//add entire arrayList here
			for(int x:mp.get(d))
				B.add(x);
			d+=par[d];
		}
		while(B.size()>0)
		{
			int a=B.remove(0);
			 A=ask(a,N);
			 for(int i=0; i<N; i++)
			 {
				 if(A[i]==1)
				 {
					 edge[i+1][a]=true;
					 edge[a][i+1]=true;
				 }
			 }
		}
		for(int i=1; i<=N; i++)
		{
			for(int j=1; j<=N; j++)
			{
				if(edge[i][j])
				{
					ans.append(i+" "+j+"\n");
					edge[i][j]=false;
					edge[j][i]=false;
				}
			}
		}
		System.out.println("!");
		System.out.println(ans);

	}
	static void f(int dp[][],int d,int A[],int par[])
	{
		if(A[d]==0)return ;
		f(dp,d+1,A,par);
		dp[0][d]=A[d]+Math.min(dp[0][d+1], dp[1][d+1]);
		if(dp[0][d+1]<dp[1][d+1])
		{
			par[d]=1;
		}
		else par[d]=2;
		dp[1][d]=dp[0][d+1];

	}
	static int [] ask(int a,int N)
	{
		System.out.println("? "+a);
		int A[]=input(N);
		return A;
	}
	static boolean isPalin(char X[])
	{
		int l=0,r=X.length-1;
		while(l<=r)
		{
			if(X[l]!=X[r])return false;
			l++;
			r--;
		}
		return true;
	}

	static boolean isSorted(int A[])
	{
		int N=A.length;
		for(int i=0; i<N-1; i++)
		{
			if(A[i]>A[i+1])return false;
		}

		return true;		
	}
	static int f1(int x,ArrayList<Integer> A)
	{
		int l=-1,r=A.size();
		while(r-l>1)
		{
			int m=(l+r)/2;
			int a=A.get(m);
			if(a<x)l=m;
			else r=m;
		}		
		return l;
	}






	static int max(int a,int b,int c)
	{
		return Math.max(a, Math.max(c, b));
	}
	static int value(char X[],char Y[])
	{
		int c=0;
		for(int i=0; i<7; i++)
		{
			if(Y[i]=='1' && X[i]=='0')return -1;
			if(X[i]=='1' && Y[i]=='0')c++;
		}
		return c;
	}
	static boolean isValid(int i,int j)
	{
		if(i<0 || i>=N)return false;
		if(j<0 || j>=M)return false;
		return true;
	}

	static long fact(long N)
	{
		long num=1L;
		while(N>=1)
		{
			num=((num%mod)*(N%mod))%mod;
			N--;
		}
		return num;
	}
	static boolean reverse(long A[],int l,int r)
	{
		while(l<r)
		{
			long t=A[l];
			A[l]=A[r];
			A[r]=t;
			l++;
			r--;
		}
		if(isSorted(A))return true;
		else return false;
	}
	static boolean isSorted(long A[])
	{
		for(int i=1; i<A.length; i++)if(A[i]<A[i-1])return false;
		return true;
	}
	static boolean isPalindrome(char X[],int l,int r)
	{
		while(l<r)
		{
			if(X[l]!=X[r])return false;
			l++; r--;
		}
		return true;
	}

	static long min(long a,long b,long c)
	{
		return Math.min(a, Math.min(c, b));
	}

	static void print(int a)
	{
		System.out.println("! "+a);
	}
	static int find(int a)
	{
		if(par[a]<0)return a;
		return par[a]=find(par[a]);
	}
	static void union(int a,int b)
	{
		a=find(a);
		b=find(b);
		if(a!=b)
		{
			par[a]+=par[b]; //transfers the size
			par[b]=a;       //changes the parent
		}
	}
	static void swap(char A[],int a,int b)
	{
		char ch=A[a];
		A[a]=A[b];
		A[b]=ch;
	}

	static void sort(long[] a) //check for long
	{
		ArrayList<Long> l=new ArrayList<>();
		for (long i:a) l.add(i);
		Collections.sort(l);
		for (int i=0; i<a.length; i++) a[i]=l.get(i);
	}
	static void setGraph(int N)
	{
		g=new ArrayList[N+1];		
		og=new ArrayList[N+1];

		val=new int[N+1];
		for(int i=0; i<=N; i++)
		{
			og[i]=new ArrayList<Integer>();			
			g[i]=new ArrayList<Integer>();
		}
	}

	static  long pow(long a,long b)
	{
		long mod=1000000007;
		long pow=1;
		long x=a;
		while(b!=0)
		{
			if((b&1)!=0)pow=(pow*x)%mod;
			x=(x*x)%mod;
			b/=2;
		}
		return pow;
	}
	static long toggleBits(long x)//one's complement || Toggle bits
	{
		int n=(int)(Math.floor(Math.log(x)/Math.log(2)))+1;

		return ((1<<n)-1)^x;
	}
	static int countBits(long a)
	{
		return (int)(Math.log(a)/Math.log(2)+1);
	}
	static void sort(int[] a) {
		ArrayList<Integer> l=new ArrayList<>();
		for (int i:a) l.add(i);
		Collections.sort(l);
		for (int i=0; i<a.length; i++) a[i]=l.get(i);
	}
	static boolean isPrime(long N)
	{
		if (N<=1)  return false; 
		if (N<=3)  return true; 
		if (N%2 == 0 || N%3 == 0) return false; 
		for (int i=5; i*i<=N; i=i+6) 
			if (N%i == 0 || N%(i+2) == 0) 
				return false; 
		return true; 
	}
	static long GCD(long a,long b) 
	{
		if(b==0)
		{
			return a;
		}
		else return GCD(b,a%b );
	}

	//Debugging Functions Starts

	static void print(char A[])
	{
		for(char c:A)System.out.print(c+" ");
		System.out.println();
	}
	static void print(boolean A[])
	{
		for(boolean c:A)System.out.print(c+" ");
		System.out.println();
	}
	static void print(int A[])
	{
		for(int a:A)System.out.print(a+" ");
		System.out.println();	
	}
	static void print(long A[])
	{
		for(long i:A)System.out.print(i+ " ");
		System.out.println();

	}
	static void print(ArrayList<Integer> A)
	{
		for(int a:A)System.out.print(a+" ");
		System.out.println();
	}

	//Debugging Functions END
	//----------------------
	//IO FUNCTIONS STARTS
	static HashMap<Integer,Integer> Hash(int A[])
	{
		HashMap<Integer,Integer> mp=new HashMap<>();
		for(int a:A)
		{
			int f=mp.getOrDefault(a,0)+1;
			mp.put(a, f);
		}
		return mp;
	}
	static int i()
	{
		return in.nextInt();
	}

	static long l()
	{
		return in.nextLong();
	}

	static int[] input(int N){
		int A[]=new int[N];
		for(int i=0; i<N; i++)
		{
			A[i]=in.nextInt();
		}
		return A;
	}

	static long[] inputLong(int N)     {
		long A[]=new long[N];
		for(int i=0; i<A.length; i++)A[i]=in.nextLong();
		return A;
	}

	//IO FUNCTIONS END

}

class node 
{
	int min,i,last;
	StringBuilder sb;
	node(int x)
	{
		sb=new StringBuilder();
		min=x;
	}
	node(int x,int a,int b)
	{		
		this.min=x;
		i=a;
		last=b;
	}

}

//Code For FastReader
//Code For FastReader
//Code For FastReader
//Code For FastReader
class FastReader
{
	BufferedReader br;
	StringTokenizer st;
	public FastReader()
	{
		br=new BufferedReader(new InputStreamReader(System.in));
	}

	String next()
	{
		while(st==null || !st.hasMoreElements())
		{
			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());
	}
	//gey
	double nextDouble()
	{
		return Double.parseDouble(next());
	}

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

}