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

//In rememberance to GOD//
//May GOD take care to me,my family and all the creatures//
#include<bits/stdc++.h>
using namespace std;
using namespace chrono;



#define ll long long
#define int ll   
#define mod ((int)1e9)+7
#define pii pair<int,int>
#define pic pair<int,char>
#define vi vector<int>
#define vvi vector<vi>
#define vc vector<char>
#define vpi vector<pii>
#define vpic vector<pair<int,char>>
#define vpid vector<pair<int,double>>
#define vb vector<bool>
#define vvb vector<vb>
#define vs vector<string>
#define INF (ll)1e15
#define all(x)  (x).begin(),(x).end()
#define allr(x) (x).rbegin(),(x).rend()
#define pb push_back
#define rep(i, begin, end) for (__typeof(end) i = (begin) - ((begin) > (end)); i != (end) - ((begin) > (end)); i += 1 - 2 * ((begin) > (end)))
#define eb emplace_back

#define error(args...) { string _s = #args; replace(_s.begin(), _s.end(), ',', ' '); stringstream _ss(_s); istream_iterator<string> _it(_ss); err(_it, args); }

void err(istream_iterator<string> it) {}
template<typename T, typename... Args>
void err(istream_iterator<string> it, T a, Args... args) {
	cerr << *it << " = " << a << endl;
	err(++it, args...);
}

#define debug(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)
#define time__(d) \
    for ( \
        auto blockTime = make_pair(chrono::high_resolution_clock::now(), true); \
        blockTime.second; \
        debug("%s: %lld ms\n", d, chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now() - blockTime.first).count()), blockTime.second = false \
    )

//fill memset iota function
// use 1ll<<number(left shift)

const int N=4e5+1;


// int primes[N];
// // int spf[N];
// vector<int> pr;
// // vi pr1;

// void sieve(){

//     // iota(spf+2,spf+N,2);

// 	for(ll i=2;i<N;i++){
// 		if(primes[i]==0){
// 		 	pr.eb(i);
// 			for(ll j=i*i;j<N;j+=i){
//                 // if(spf[j]==j)
//                 // spf[j]=i;
// 				primes[j]=1;
// 			}
// 		}
// 		// primes[i]^=1;
// 	}



// }
// nloglogn




ll gcd(ll a, ll b) {if (b > a) {return gcd(b, a);} if (b == 0) {return a;} return gcd(b, a % b);}
void swap(int &x, int &y) {int temp = x; x = y; y = temp;}
ll mod_add(ll a, ll b, ll m) {a = a % m; b = b % m; return (((a + b) % m) + m) % m;}
ll mod_mul(ll a, ll b, ll m) {a = a % m; b = b % m; return (((a * b) % m) + m) % m;}
ll mod_sub(ll a, ll b, ll m) {a = a % m; b = b % m; return (((a - b) % m) + m) % m;}
ll power(ll x, ll y) {ll res = 1;while (y > 0){ if (y & 1) res = (res*x)%mod;y = y>>1;x = x*x%mod;}return res;}
//avoid it in power of 2.
//logn implementation(y)
//divisors in o(sqrt(n)) and no of divisors less than 2*(n^1/2);//128(max divisors for n<=1e5)
//for n>=1e5 no of divisors is less than n^1/3;
// use i&1 instead of i%2
//stoll convert string to integer(long long)
//  if(a>b) { return a/b+fun(b,a%b);} else return b/a+fun(a,b%a); technique of mod and minus similar answer but diffent time complexity.
// if(a>b) { return 1+fun(b,a-b);} else return 1+fun(a,b-a);
// class cmp { public:
//     bool operator()(const pair<int,pii> &p1,const pair<int,pii> &p2) const { if(p1.first!=p2.first) return p1.first>p2.first;else
//         {
//             if(p1.second.first!=p2.second.first)
//             {
//                 return p1.second.first>p2.second.first;
//             }
//             else
//             {
//                 return p1.second.second>p2.second.second;
//             }
            
//         }
        
//     }
 
// };
// instead of priority queue i use set defined with cmp class strictly using '=' sign in comparisons(work as multi set)
//erase function with values won't work
//to modify a set i use cmp class stictly without using '=' sign in comparions,example above!!!!  
//erase function with values will work
//sizeof terminate on spaces in charater array........
//prime gap o((logn)^2) approx
//prime gap till 1e9 is 282
//prime gap<sqrt(n)
//no of primes N/lnN till N
//we just have to remove a single element in order to remove equal sum partition subsequences and that too having minimun num of multiplication of two.
//topological sorting in queue(element with 0 indegree) 
//no of palindromic numbers till n is sqrt(n)
//int *int =int      ll*int=ll
//for prime factoristion in sqrt(n) find primes upto sqrt(n) then iterate over them and check if the number is divisible by it,if yes then divide untill it is not divisible,
// remember to reduce the number
// int sum=0;
//     for(auto x:pr)
//     {
//         if(x*x>a)
//         break;
//         else
//         {
//             while(a%x==0)
//             {
//                 sum++;
//                 a/=x;
//             }
//         }
//     }

//     if(a>1)
//     sum++;
// sqrt(N)/ln(sqrt(N))
//minimizing the difference sort->iterate->increase the prefix decrease the suffix by k->check;







/////////////////////////////////////// CODE ////////////////////////////////////////////////


// int dx[] = {1,0,-1,0};
// int dy[] = {0,1,0,-1};
// int dr[] = {1,1,0,-1,-1,-1, 0, 1}; // trick to explore an implicit 2D grid
// int dc[] = {0,1,1, 1, 0,-1,-1,-1};





// vi adj[N];
// int visited[N];
// int par[N];
// int d[N];
// int dp[N];



// int binary_searching(vi &v,int num,int sum)
// {
//     int n=v.size();
//     int start=0;
//     int end=n-1;

//     while(start+1!=end)
//     {
//         int mid=(start+end)/2;
//         if(sum-v[mid]>=num)
//         {
//             start=mid;
//         }
//         else
//         end=mid;
//     }

//     if(sum-v[start]==num)
//     {
//         return start;
//     }
//     else
//     {
//         return -1;
//     }
    

    
    
// }



// int index=-1;




// void dfs(int current,double & maxi,vector<double>&v)
// {
//     visited[current]=true;

//     for(auto x:adj[current])
//     {
//         int node=x.first;
//         int weight=x.second;

//         maxi=max(maxi,(v[current]+v[node])/weight);
//         if(!visited[node])
//         {
//             dfs(node,maxi,v);
//         }


//     }

// }

// int query(int l,int r)
// {
//     cout<<"? "<<l<<" "<<r<<'\n';
//     fflush(stdout);
//     int x;
//     cin>>x;

//     return x;
// }


// int binary_searching(int n,int k)
// {
//     int start=0;
//     int end=n;

//     while(start+1!=end)
//     {
//         int mid=(start+end)/2;
//         int o=query(1,mid);
//         int z=mid-o;

//         if(z>=k)
//         {
//             end=mid;
//         }
//         else
//         {
//             start=mid;
//         }
        
//     }
//     return end;
// }


// void dfs(int current,vvi &dp,vi &l,vi &r,int &root,int parent)
// {
//     visited[current]=true;


//     for(auto x:adj[current])
//     {
//         if(!visited[x])
//         dfs(x,dp,l,r,root,current);
//     }



//     //l
//     int temp1=0;
//     int temp2=0;

//     for(auto x:adj[current])
//     {
//         if(x!=parent)
//         {
//         temp1+=max(abs(l[current]-l[x])+dp[x][0],abs(l[current]-r[x])+dp[x][1]);
//         temp2+=max(abs(r[current]-l[x])+dp[x][0],abs(r[current]-r[x])+dp[x][1]);
//         }

//     }
//     dp[current][0]=temp1;
//     dp[current][1]=temp2;





// }








// int binary_searching(vi &v,int r)
// {
//     int n=v.size();
//     int start=0;
//     int end=n;

//     while(start+1!=end)
//     {
//         int mid=(start+end)/2;
//         if(v[mid]<=r)
//         {
//             start=mid;
//         }
//         else
//         {
//             end=mid;
//         }
//     }

//     if(v[start]<=r)
//     {
//         return start;
//     }
    
//     return -1;

// }




void solve()
{
    int n;
    cin>>n;


    cout<<"? "<<"1"<<'\n';
    fflush(stdout);

    vi d(n+1,0);
    for(int i=1;i<=n;i++)
    {
        cin>>d[i];
    }

    vi e,o;
    set<pii> edges;

    for(int i=1;i<=n;i++)
    {
        if(d[i]%2==0)
        {
            if(d[i]!=0)
            e.eb(i);
        }
        else
        {
        o.eb(i);
        if(d[i]==1)
        {
            edges.emplace(1,i);
        }
        }
    }


    if(e.size()<=o.size())
    {
        for(int j=0;j<e.size();j++)
        {
            cout<<"? "<<e[j]<<'\n';
            fflush(stdout);
        

        for(int i=1;i<=n;i++)
        {
            cin>>d[i];
            if(d[i]==1)
            {
                edges.emplace(e[j],i);
            }
        }
        }
    }
    else
    {
        edges.clear();
        for(int j=0;j<o.size();j++)
        {
            cout<<"? "<<o[j]<<'\n';
            fflush(stdout);
        


        for(int i=1;i<=n;i++)
        {
            cin>>d[i];
            if(d[i]==1)
            {
                edges.emplace(o[j],i);
            }
        }
        }
    }
    cout<<"!"<<'\n';

    for(auto &[x,y]:edges)
    {
       cout<<x<<" "<<y<<'\n';
    }






























}









int32_t main()
{
    #ifndef ONLINE_JUDGE
        freopen("input.txt", "r", stdin);
        freopen("output.txt", "w", stdout);
    #endif
    // freopen("input.txt","rt",stdin);
	// freopen("output.txt","wt",stdout);

    // ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

    int t;
    // cin>>t;


    // sieve();

    
    t=1;

    
   

    

    for(int i=1;i<=t;i++)
    {
        solve();
        // IshanMakkar
        // time__("solve");
    }


    return 0;
}