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

/* Aditya0412 */
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
template <typename T>
using ordered_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
#define int long long
#define pb push_back
#define vii vector<int>
#define pii pair<int, int>
#define fi first
#define all(a) (a).begin(), (a).end()
#define si(x) (int)((x).size())
#define debug(x)                         \
    cout << #x << " is " << (x) << endl; \
    cout.flush();
#define se second
#define endl '\n'
#define f(i, a, b) for (int i = a; i < b; i++)
#define mem(a, x) memset(a, x, sizeof(a))
#define fastIO               \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
// const int  mod=998244353;
const int mod = 1000000000 + 7;
const int N = 1000000 + 6;
#define M_PI 3.14159265358979323846
// In C++, comparator should return false if its arguments are equal.
// === Debug macro starts here ===
int recur_depth = 0;
#ifdef DEBUG
#define dbg(x)                                                                                                                         \
    {                                                                                                                                  \
        ++recur_depth;                                                                                                                 \
        auto x_ = x;                                                                                                                   \
        --recur_depth;                                                                                                                 \
        cerr << string(recur_depth, '\t') << "\e[91m" << __func__ << ":" << __LINE__ << "\t" << #x << " = " << x_ << "\e[39m" << endl; \
    }
#else
#define dbg(x)
#endif
template <typename Ostream, typename Cont>
typename enable_if<is_same<Ostream, ostream>::value, Ostream &>::type operator<<(Ostream &os, const Cont &v)
{
    os << "[";
    for (auto &x : v)
    {
        os << x << ", ";
    }
    return os << "]";
}
template <typename Ostream, typename... Ts>
Ostream &operator<<(Ostream &os, const pair<Ts...> &p)
{
    return os << "{" << p.first << ", " << p.second << "}";
}
// === Debug macro ends here ===
int dist[2005][2005];
void fun(int x)
{
    cout << "? " << x << endl;
    cout.flush();
}
set<int> graph[N];
void solve()
{
    int n;
    cin >> n;
    set<int> s;
    f(i, 1, n + 1)
    {
        s.insert(i);
    }
    bool vis[n + 5];
    mem(vis, false);
    queue<pair<int, int>> q;
    q.push({1, -1});
    vis[1] = true;
    s.erase(s.find(1));
    while (!q.empty() and si(s))
    {
        int node = q.front().fi;
        int par = q.front().se;
        q.pop();
        vis[node] = true;
        if (s.find(node) != s.end())
            s.erase(s.find(node));
        fun(node);
        // f(i,1,n+1)
        // {
        queue<int> qq[n + 1];
        int dis[n + 5];
        f(i, 1, n + 1)
        {
            cin >> dis[i];
            dist[node][i] = dis[i];
            dist[i][node] = dis[i];
            if (par == -1)
            {
                qq[dis[i]].push(i);
            }
            else
            {
                // node par i
                if (dist[node][i] < dist[par][i] and !vis[i])
                {
                    qq[dis[i]].push(i);
                }
            }
        }
        // if(x==1)
        // {
        //     graph[node].insert(i);
        //     graph[i].insert(node);
        //     if(s.find(i)!=s.end())
        //     s.erase(s.find(i));
        //     if(!vis[i])
        //     {q.push(i);vis[i]=true;}
        // }
        bool bo = true;
        int tail = node;
        for (int d = 1; d <= n - 1 and bo; d++)
        {
            if (si(qq[d]) == 0)
            {
                break;
            }
            else if (si(qq[d]) > 1)
            {
                bo = false;

                while (!qq[d].empty())
                {
                    int nxt = qq[d].front();
                    qq[d].pop();
                    graph[tail].insert(nxt);
                    graph[nxt].insert(tail);
                    vis[nxt] = true;
                    if (s.find(nxt) != s.end())
                        s.erase(s.find(nxt));
                    q.push({nxt, node});
                }
                break;
            }
            else
            {
                int nxt = qq[d].front();
                qq[d].pop();
                graph[tail].insert(nxt);
                graph[nxt].insert(tail);
                vis[nxt] = true;
                if (s.find(nxt) != s.end())
                    s.erase(s.find(nxt));
                tail = nxt;
            }
        }

        // dbg(s);
    }
    set<pair<int, int>> ans;
    for (int i = 1; i <= n; i++)
    {
        for (auto x : graph[i])
        {
            int a = i, b = x;
            if (a > b)
                swap(a, b);
            ans.insert({a, b});
        }
    }
    cout << "!" << endl;
    cout.flush();
    for (auto [a, b] : ans)
        cout << a << ' ' << b << endl;
    cout.flush();
}
int32_t main()
{
    fastIO;
    int tc = 1;
    // cin>>tc;
    while (tc--)
    {
        solve();
    }
}