Problem: icpc2024_asia_nanjing_g
Difficulty: easy
Categories: graph, search
Time limit: 1000 ms, memory limit: 1024 MB

# This is an interactive problem.

Given a binary tree with $n$ vertices, your task is to find a special vertex $s$ in the tree with at most $p = \lfloor \log _ { 2 } n \rfloor$ queries. That is to say, $p$ is the largest integer such that $2 ^ { p } \leq n$ .

Each query consists of two different vertices $u$ and $v$ . The interactor will output an integer $t$ $( 0 \leq t \leq 2$ ) as the answer. Let $d ( a , b )$ be the number of edges on the simple path from vertex $a$ to vertex $b$ .

• If $t = 0$ , then vertex $u$ is nearer to the special vertex. That is, $d ( u , s ) < d ( v , s )$ .   
• If $t = 1$ , then the distances from $u$ and $v$ to the special vertex are the same. That is, $d ( u , s ) = d ( v , s )$ .   
• If $t = 2$ , then vertex $\boldsymbol { v }$ is nearer to the special vertex. That is, $d ( u , s ) > d ( v , s )$ .

Note that the interactor is adaptive, meaning that the answer for each test case is not pre-determined. The interactor can determine the special vertex according to your queries, as long as its answer does not conflict with the previous queries and answers.

# Input

There are multiple test cases. The first line of the input contains an integer $T$ indicating the number of test cases. For each test case:

The first line contains an integer $n$ ( $2 \leq n \leq 1 0 ^ { 5 }$ ) indicating the number of vertices in the binary tree.

For the following $n$ lines, the $i$ -th line contains two integers $x _ { i }$ and $y _ { i }$ $0 \leq x _ { i } , y _ { i } \leq n$ ), indicating the left and right child of the $i$ -th vertex. If $x _ { i } = 0$ , then the $i$ -th vertex has no left child; if $y _ { i } = 0$ , then the $i$ -th vertex has no right child.

It is guaranteed that the sum of $n$ for all test cases will not exceed $2 \times 1 0 ^ { 5 }$ .

# Interaction Protocol

To ask a query, output one line. First output ? followed by a space, then print two different integers $u$ and $v$ $( 1 \leq u , v \leq n )$ separated by a space. After flushing your output, your program should read a single integer $t$ indicating the answer to your query.

If you want to guess the special vertex, output one line. First output ! followed by a space, then print an integer $s$ ( $1 \leq s \leq n$ ) indicating the special vertex. After flushing your output, your program should continue processing the next test case, or exit immediately if there are no more test cases. Note that your guess does not count as a query.

To flush your output, you can use:

• fflush(stdout) (if you use printf) or cout.flush() (if you use cout) in C and C++.   
• System.out.flush() in Java.   
• stdout.flush() in Python.

# Example

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td></td><td></td></tr><tr><td>2</td><td></td></tr><tr><td>00</td><td></td></tr><tr><td>15</td><td></td></tr><tr><td>24</td><td></td></tr><tr><td>00</td><td></td></tr><tr><td>00</td><td>？51</td></tr><tr><td>1</td><td>？14</td></tr><tr><td>0</td><td></td></tr><tr><td>2</td><td>！2</td></tr><tr><td>02</td><td></td></tr><tr><td>00</td><td></td></tr><tr><td>2</td><td>？21</td></tr></table>

Non-adaptive interactor (C++):
#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;




static long long queries_used = 0;
static long long total_query_limit = 0;
static const long long HARD_CAP = 3000000; 



static void log_metrics() {
    try {
        tout << "queries=" << queries_used << "\n";
        tout << "query_limit=" << total_query_limit << "\n";
    } catch (...) {
        
    }
}


template<typename... Args>
static void finish(TResult verdict, const char* fmt, Args... args) {
    log_metrics();
    quitf(verdict, fmt, args...);
}


static void final_log() {
    log_metrics();
}




const int MAXN = 200005;
vector<int> adj[MAXN];
int depth_arr[MAXN];
int up[MAXN][20];       
int parent_node[MAXN];  


void dfs(int u, int p, int d) {
    depth_arr[u] = d;
    up[u][0] = p;
    for (int i = 1; i < 20; i++) {
        up[u][i] = up[up[u][i-1]][i-1];
    }
    for (int v : adj[u]) {
        if (v != p) dfs(v, u, d + 1);
    }
}


int get_lca(int u, int v) {
    if (depth_arr[u] < depth_arr[v]) swap(u, v);
    for (int i = 19; i >= 0; i--) {
        if (depth_arr[u] - (1 << i) >= depth_arr[v]) {
            u = up[u][i];
        }
    }
    if (u == v) return u;
    for (int i = 19; i >= 0; i--) {
        if (up[u][i] != up[v][i]) {
            u = up[u][i];
            v = up[v][i];
        }
    }
    return up[u][0];
}


int get_dist(int u, int v) {
    return depth_arr[u] + depth_arr[v] - 2 * depth_arr[get_lca(u, v)];
}




int main(int argc, char** argv) {
    
    registerInteraction(argc, argv);
    atexit(final_log);

    
    int T = inf.readInt();
    cout << T << endl; 

    for (int t = 1; t <= T; t++) {
        
        int n = inf.readInt();

        
        for (int i = 0; i <= n; i++) {
            adj[i].clear();
            parent_node[i] = 0;
        }

        
        
        vector<pair<int, int>> children(n + 1);
        for (int i = 1; i <= n; i++) {
            int l = inf.readInt();
            int r = inf.readInt();
            children[i] = {l, r};
            
            
            if (l != 0) {
                adj[i].push_back(l);
                adj[l].push_back(i);
                parent_node[l] = i;
            }
            if (r != 0) {
                adj[i].push_back(r);
                adj[r].push_back(i);
                parent_node[r] = i;
            }
        }

        
        cout << n << "\n";
        for (int i = 1; i <= n; i++) {
            cout << children[i].first << " " << children[i].second << "\n";
        }
        cout << endl; 

        
        int s = inf.readInt();

        
        int root = 1;
        
        
        while (parent_node[root] != 0 && root <= n) {
            root = parent_node[root];
        }
        dfs(root, root, 0);

        
        
        int limit = 0;
        if (n >= 1) {
            limit = (int)floor(log2((double)n));
        }
        total_query_limit += limit;
        log_metrics(); 

        
        while (true) {
            if (queries_used > HARD_CAP) {
                finish(_pe, "Hard query limit exceeded (infinite loop protection)");
            }

            string token;
            if (!(cin >> token)) {
                finish(_wa, "Unexpected EOF (expected query or guess)");
            }
            
            
            if (token[0] == '!') {
                
                int guess;
                if (token.size() > 1) {
                    
                    try {
                        guess = stoi(token.substr(1));
                    } catch (...) { finish(_pe, "Invalid integer format in guess"); }
                } else {
                    
                    if (!(cin >> guess)) finish(_wa, "Expected guess after '!'");
                }

                if (guess == s) {
                    
                    break;
                } else {
                    finish(_wa, "Incorrect guess: expected %d, got %d", s, guess);
                }
            } else {
                
                int u, v;
                if (token[0] == '?') {
                    if (token.size() > 1) {
                        
                        try {
                            u = stoi(token.substr(1));
                        } catch (...) { finish(_pe, "Invalid integer format in query"); }
                        if (!(cin >> v)) finish(_wa, "Expected second vertex v");
                    } else {
                        
                        if (!(cin >> u >> v)) finish(_wa, "Expected vertices u and v");
                    }
                } else {
                    
                    try {
                        u = stoi(token);
                    } catch (...) { finish(_pe, "Invalid token '%s'", token.c_str()); }
                    if (!(cin >> v)) finish(_wa, "Expected second vertex v");
                }

                
                if (u < 1 || u > n || v < 1 || v > n) {
                    finish(_pe, "Query vertices %d %d out of range [1, %d]", u, v, n);
                }
                if (u == v) {
                    finish(_pe, "Query vertices must be distinct (%d vs %d)", u, v);
                }

                queries_used++;
                
                if ((queries_used & 127) == 0) log_metrics();

                
                int du = get_dist(u, s);
                int dv = get_dist(v, s);
                
                int ans;
                if (du < dv) ans = 0;      
                else if (du == dv) ans = 1; 
                else ans = 2;              

                cout << ans << endl; 
            }
        }
    }

    
    string dummy;
    if (cin >> dummy) {
        finish(_wa, "Extra output detected after last test case");
    }

    finish(_ok, "All %d test cases passed", T);
    return 0;
}


Adaptive interactor (C++):
#include "testlib.h"
#include <bits/stdc++.h>

using namespace std;


static long long queries = 0;
static long long query_limit = 0;
static const long long HARD_CAP = 200000;



void log_metrics() {
    try {
        tout << "queries=" << queries << endl;
        tout << "query_limit=" << query_limit << endl;
    } catch (...) {
        
    }
}


void finish(TResult verdict, const string& msg) {
    log_metrics();
    quitf(verdict, "%s", msg.c_str());
}

struct TestCase {
    int n;
    vector<pair<int, int>> children; 
    vector<vector<int>> up;          
    vector<int> depth;               
    int budget;                      
};



int calc_dist(int u, int v, const vector<int>& depth, const vector<vector<int>>& up) {
    if (depth[u] < depth[v]) swap(u, v);
    int diff = depth[u] - depth[v];
    
    
    int u_curr = u;
    for (int i = 0; i < 20; i++) {
        if ((diff >> i) & 1) {
            u_curr = up[u_curr][i];
        }
    }
    
    int lca = u_curr;
    if (u_curr != v) {
        int v_curr = v;
        
        for (int i = 19; i >= 0; i--) {
            if (up[u_curr][i] != up[v_curr][i]) {
                u_curr = up[u_curr][i];
                v_curr = up[v_curr][i];
            }
        }
        lca = up[u_curr][0];
    }
    
    return depth[u] + depth[v] - 2 * depth[lca];
}

int main(int argc, char** argv) {
    
    registerInteraction(argc, argv);
    atexit(log_metrics); 

    
    
    int T = inf.readInt();
    vector<TestCase> cases;
    cases.reserve(T);

    for (int i = 0; i < T; i++) {
        TestCase tc;
        tc.n = inf.readInt();
        tc.children.resize(tc.n + 1);
        vector<int> parent(tc.n + 1, 0);
        
        for (int j = 1; j <= tc.n; j++) {
            int l = inf.readInt();
            int r = inf.readInt();
            tc.children[j] = {l, r};
            if (l) parent[l] = j;
            if (r) parent[r] = j;
        }
        
        
        int root = 1;
        while (parent[root] != 0) root = parent[root];
        
        
        tc.depth.resize(tc.n + 1);
        tc.up.assign(tc.n + 1, vector<int>(20));
        
        queue<int> q;
        q.push(root);
        tc.depth[root] = 0;
        
        for(int k=0; k<20; k++) tc.up[root][k] = root;
        
        while(!q.empty()){
            int u = q.front(); q.pop();
            
            auto process = [&](int child){
                tc.depth[child] = tc.depth[u] + 1;
                tc.up[child][0] = u;
                for(int k=1; k<20; k++){
                    tc.up[child][k] = tc.up[tc.up[child][k-1]][k-1];
                }
                q.push(child);
            };
            
            int l = tc.children[u].first;
            int r = tc.children[u].second;
            if (l) process(l);
            if (r) process(r);
        }
        
        
        
        int val = 1; 
        int p = 0;
        while ((long long)val * 2 <= tc.n) {
            val *= 2;
            p++;
        }
        tc.budget = p;
        query_limit += p;
        
        cases.push_back(tc);
    }
    
    
    log_metrics();
    
    
    cout << T << endl;
    
    
    for (const auto& tc : cases) {
        
        cout << tc.n << "\n";
        for (int i = 1; i <= tc.n; i++) {
            cout << tc.children[i].first << " " << tc.children[i].second << "\n";
        }
        cout << endl; 
        
        
        vector<int> S(tc.n);
        iota(S.begin(), S.end(), 1); 
        
        bool active = true;
        while (active) {
            if (queries > HARD_CAP) {
                finish(_pe, "Hard query limit exceeded (spam protection)");
            }
            
            string type;
            if (!(cin >> type)) {
                finish(_pe, "Unexpected EOF while waiting for query type");
            }
            
            if (type == "!") {
                int guess;
                if (!(cin >> guess)) finish(_pe, "Invalid guess format");
                
                
                
                
                
                
                
                bool feasible = false;
                for (int x : S) if (x == guess) feasible = true;
                
                if (!feasible) {
                    finish(_wa, "Guessed vertex is inconsistent with previous query answers");
                }
                if (S.size() > 1) {
                    finish(_wa, "Guessed vertex is not uniquely determined (ambiguous)");
                }
                
                
                active = false; 
                
            } else if (type == "?" || type == "？") {
                int u, v;
                cin >> u >> v;
                if (cin.fail()) finish(_pe, "Invalid query arguments");
                
                if (u < 1 || u > tc.n || v < 1 || v > tc.n || u == v) {
                    finish(_pe, "Invalid query parameters (out of range or u == v)");
                }
                
                queries++;
                
                if (queries % 100 == 0) log_metrics();
                
                
                
                vector<int> S0, S1, S2;
                S0.reserve(S.size());
                S1.reserve(S.size());
                S2.reserve(S.size());
                
                for (int s : S) {
                    int du = calc_dist(u, s, tc.depth, tc.up);
                    int dv = calc_dist(v, s, tc.depth, tc.up);
                    if (du < dv) S0.push_back(s);
                    else if (du == dv) S1.push_back(s);
                    else S2.push_back(s);
                }
                
                
                int ans = 0;
                size_t max_sz = S0.size();
                
                if (S1.size() > max_sz) {
                    max_sz = S1.size();
                    ans = 1;
                }
                if (S2.size() > max_sz) {
                    max_sz = S2.size();
                    ans = 2;
                }
                
                
                if (ans == 0) S = move(S0);
                else if (ans == 1) S = move(S1);
                else S = move(S2);
                
                cout << ans << endl; 
                
            } else {
                finish(_pe, "Unknown query type token");
            }
        }
    }
    
    finish(_ok, "All cases solved successfully");
    return 0;
}


Test generator (C++):
#include "testlib.h"
#include <bits/stdc++.h>

using namespace std;

int main(int argc, char* argv[]) {
    registerGen(argc, argv, 1);

    
    
    string mode = opt<string>("mode", "non");
    
    int n_opt = opt<int>("n", -1);
    
    
    
    long long seed = 0;
    if (argc > 1) seed = atoll(argv[1]);

    int n;
    if (n_opt != -1) {
        n = n_opt;
    } else {
        
        if (seed == 1) n = 5;            
        else if (seed == 2) n = 10;      
        else if (seed == 3) n = 15;      
        else if (seed == 4) n = 100;     
        else if (seed == 5) n = 100000;  
        else if (seed == 6) n = 100000;  
        else n = rnd.next(90000, 100000); 
    }

    if (n < 2) n = 2; 

    
    
    int type = 0;
    if (seed == 1 || seed == 2 || seed == 4 || seed == 5) type = 1;
    else if (seed == 3 || seed == 6) type = 2;
    else type = 0;

    
    
    vector<int> lc(n + 1, 0), rc(n + 1, 0);

    
    auto add_child = [&](int p, int c) {
        
        
        if (lc[p] == 0 && rc[p] == 0) {
            if (rnd.next(2)) lc[p] = c;
            else rc[p] = c;
        } else if (lc[p] == 0) {
            lc[p] = c;
        } else {
            rc[p] = c;
        }
    };

    if (type == 1) { 
        
        for (int i = 2; i <= n; ++i) {
            add_child(i - 1, i);
        }
    } else if (type == 2) { 
        
        for (int i = 2; i <= n; ++i) {
            add_child(i / 2, i);
        }
    } else { 
        
        
        
        vector<int> candidates;
        candidates.push_back(1);
        for (int i = 2; i <= n; ++i) {
            int idx = rnd.next(candidates.size());
            int p = candidates[idx];
            add_child(p, i);
            
            if (lc[p] != 0 && rc[p] != 0) {
                candidates[idx] = candidates.back();
                candidates.pop_back();
            }
            candidates.push_back(i);
        }
    }

    
    
    vector<int> p(n + 1);
    for (int i = 1; i <= n; ++i) p[i] = i;
    shuffle(p.begin() + 1, p.end());

    
    vector<pair<int, int>> adj(n + 1);
    for (int i = 1; i <= n; ++i) {
        int u_label = p[i];
        int l_label = (lc[i] == 0 ? 0 : p[lc[i]]);
        int r_label = (rc[i] == 0 ? 0 : p[rc[i]]);
        adj[u_label] = {l_label, r_label};
    }

    
    
    cout << 1 << "\n";
    cout << n << "\n";
    for (int i = 1; i <= n; ++i) {
        cout << adj[i].first << " " << adj[i].second << "\n";
    }

    
    
    if (mode == "non") {
        int s_logical = rnd.next(1, n);
        cout << p[s_logical] << "\n";
    }

    return 0;
}