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

# This is an interactive problem.

Randias has an unknown hidden tree with $n$ vertices. The tree is either a chain or a star. Randias now needs to determine whether the tree is a chain or a star. He can ask a question in the following form, but no more than $\lceil \frac { n } { 2 } \rceil + 3$ times:

• Is there an edge between vertex $u$ and vertex $v$ ( $1 \leq u , v \leq n$ , $u \neq v$ )?

Randias needs to determine which of the two kinds the tree is. Help him to ask the questions and determine the answer.

A tree is called a chain if and only if there exists a permutation $p 1 , p 2 , \ldots , p _ { n }$ such that, for every $i$ $1 \leq i < n$ ), there is an edge $( p _ { i } , p _ { i + 1 } )$ in the tree. Here, a permutation of length $n$ is an array where each integer from 1 to $n$ appears exactly once.

A tree is called a star if and only if there exists a vertex $u$ such that, for every other vertex $v$ , there is an edge $( u , v )$ in the tree.

In this problem, the interactor is adaptive, which means that the secret tree is not fixed beforehand. Instead, the interactor can change the tree arbitrarily during the interaction. Nevertheless, at every moment, the tree will be consistent with all the answers you got.

# Input

Each test contains multiple test cases. The first line contains a single integer $t$ ( $1 \leq t \leq 2 5 0$ ) denoting the number of test cases.

For each test case, the first line contains one integer $n$ ( $4 \leq n \leq 1 0 0 0$ ) denoting the number of vertices.   
It is guaranteed that the sum of $n$ over all test cases does not exceed 1000.

# Interaction Protocol

You can ask at most $\lceil \frac { n } { 2 } \rceil + 3$ questions in every test case. To ask a question, output a line of the form “? u $v ^ { \flat }$ ( $1 \leq u , v \leq n$ , $u \neq v$ ). Then you should read the response from standard input.

In response to the query, the interactor will output a line with a single integer: 1 if there is an edge between $u$ and $v$ in the tree, or 0 if there is no such edge.

To give your answer, print a line of the form “! 1” if you determined that the tree is a chain, or “ ! 2” if you determined that it is a star. The output of the answer is not counted towards the limit of $\lceil \frac { n } { 2 } \rceil + 3$ queries.

After printing the answer, your program should process the next test case, or terminate if there are no more test cases.

After printing each line, do not forget to output end of line and flush the output. To do the latter, you can use fflush(stdout) or cout.flush() in $\mathrm { C } + +$ , System.out.flush() in Java, or stdout.flush() in Python.

# Example

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>2</td><td></td></tr><tr><td></td><td>？12</td></tr><tr><td>1</td><td>？23</td></tr><tr><td>1</td><td>？34</td></tr><tr><td>1</td><td>！1</td></tr><tr><td>4</td><td>？13</td></tr><tr><td>1</td><td>？24</td></tr><tr><td>0</td><td>？12</td></tr><tr><td>0</td><td>？14</td></tr><tr><td>0</td><td>！2</td></tr></table>

Non-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; 


bool is_positive_integer(const string& s) {
    if (s.empty()) return false;
    for (char c : s) if (!isdigit(c)) return false;
    return true;
}


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());
}

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

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

    for (int i = 0; i < t; ++i) {
        
        int n = inf.readInt();
        int type = inf.readInt(); 

        
        vector<int> p;
        vector<int> pos(n + 1, 0); 
        int center = -1;           

        if (type == 1) {
            
            p.resize(n);
            for (int k = 0; k < n; ++k) {
                p[k] = inf.readInt();
                pos[p[k]] = k; 
            }
        } else {
            
            center = inf.readInt();
        }

        
        
        long long local_limit = (long long)ceil(n / 2.0) + 3;
        query_limit += local_limit;
        long long local_queries = 0;
        
        
        log_metrics();

        
        cout << n << endl;

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

            string token;
            if (!(cin >> token)) {
                finish(_pe, "Unexpected EOF (solution exited prematurely)");
            }

            if (token == "!") {
                
                int ans;
                if (!(cin >> ans)) {
                    finish(_pe, "Expected integer answer after '!'");
                }

                
                if (ans != 1 && ans != 2) {
                    finish(_wa, "Answer must be 1 or 2");
                }
                if (ans != type) {
                    finish(_wa, format("Wrong answer on case %d. Expected %d, got %d", i+1, type, ans));
                }

                
                if (local_queries > local_limit) {
                    finish(_wa, format("Query limit exceeded on case %d (used %lld, allowed %lld)", i+1, local_queries, local_limit));
                }

                
                case_active = false;
            } else {
                
                int u, v;
                if (token == "?") {
                    if (!(cin >> u >> v)) finish(_pe, "Expected u v after '?'");
                } else {
                    
                    if (!is_positive_integer(token)) {
                        finish(_pe, format("Invalid token '%s'", token.c_str()));
                    }
                    u = atoi(token.c_str());
                    if (!(cin >> v)) finish(_pe, "Expected v");
                }

                
                if (u < 1 || u > n || v < 1 || v > n || u == v) {
                    finish(_pe, format("Invalid query vertices: %d %d", u, v));
                }

                
                local_queries++;
                queries++;
                
                
                if (queries % 10 == 0) log_metrics();

                
                int res = 0;
                if (type == 1) {
                    
                    if (abs(pos[u] - pos[v]) == 1) res = 1;
                } else {
                    
                    if (u == center || v == center) res = 1;
                }

                cout << res << endl; 
            }
        }
    }

    finish(_ok, "All cases passed");
    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 bool metrics_logged = false;


static void log_metrics() {
    if (metrics_logged) return;
    metrics_logged = true;
    try {
        if (query_limit == 0) query_limit = 1000; 
        tout << "queries=" << queries << endl;
        tout << "query_limit=" << query_limit << endl;
    } catch (...) {}
}

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

const int HARD_CAP = 5000;



struct AdaptiveState {
    int n;
    set<int> star_centers; 
    vector<pair<int,int>> forbidden_edges; 
    vector<pair<int,int>> forced_edges;    

    AdaptiveState(int _n) : n(_n) {
        for(int i=1; i<=n; ++i) star_centers.insert(i);
    }

    
    bool can_be_star(bool ans_is_1, int u, int v) {
        
        set<int> next_centers = star_centers;
        if (ans_is_1) {
            
            set<int> intersect;
            if (next_centers.count(u)) intersect.insert(u);
            if (next_centers.count(v)) intersect.insert(v);
            next_centers = intersect;
        } else {
            
            next_centers.erase(u);
            next_centers.erase(v);
        }
        return !next_centers.empty();
    }

    
    bool can_be_chain(bool ans_is_1, int u, int v) {
        
        
        
        if (n >= 9) return true; 

        
        vector<int> p(n);
        iota(p.begin(), p.end(), 1); 

        
        
        
        
        
        
        
        
        do {
            bool valid = true;
            
            
            for (int i = 0; i < n - 1; ++i) {
                int a = p[i], b = p[i+1];
                if (a > b) swap(a, b);
                
                
                for (auto& fe : forbidden_edges) {
                    int fa = fe.first, fb = fe.second;
                    if (fa > fb) swap(fa, fb);
                    if (a == fa && b == fb) { valid = false; break; }
                }
                if (!valid) break;

                
                if (!ans_is_1) {
                    int cur_u = u, cur_v = v;
                    if (cur_u > cur_v) swap(cur_u, cur_v);
                    if (a == cur_u && b == cur_v) { valid = false; break; }
                }
                if (!valid) break;
            }
            if (!valid) continue;

            
            
            
            
            
            set<pair<int,int>> chain_edges;
            for (int i = 0; i < n - 1; ++i) {
                int a = p[i], b = p[i+1];
                if (a > b) swap(a, b);
                chain_edges.insert({a, b});
            }

            for (auto& fe : forced_edges) {
                int fa = fe.first, fb = fe.second;
                if (fa > fb) swap(fa, fb);
                if (chain_edges.find({fa, fb}) == chain_edges.end()) {
                    valid = false; break;
                }
            }
            if (!valid) continue;

            if (ans_is_1) {
                int cur_u = u, cur_v = v;
                if (cur_u > cur_v) swap(cur_u, cur_v);
                if (chain_edges.find({cur_u, cur_v}) == chain_edges.end()) {
                    valid = false;
                }
            }

            if (valid) return true; 
        } while (next_permutation(p.begin(), p.end()));

        return false;
    }

    void update(bool ans, int u, int v) {
        if (ans) { 
            set<int> intersect;
            if (star_centers.count(u)) intersect.insert(u);
            if (star_centers.count(v)) intersect.insert(v);
            star_centers = intersect;
            forced_edges.push_back({u, v});
        } else { 
            star_centers.erase(u);
            star_centers.erase(v);
            forbidden_edges.push_back({u, v});
        }
    }
};


void run_case(int n, bool is_fixed, int fixed_type, const vector<int>& fixed_data) {
    int current_case_limit = (n + 1) / 2 + 3;
    int current_case_queries = 0;
    
    
    set<pair<int,int>> fixed_edges;
    if (is_fixed) {
        if (fixed_type == 1) { 
            for(int i=0; i<n-1; ++i) {
                int u = fixed_data[i], v = fixed_data[i+1];
                if (u > v) swap(u, v);
                fixed_edges.insert({u, v});
            }
        } else { 
            int center = fixed_data[0];
            for(int i=1; i<=n; ++i) {
                if (i == center) continue;
                int u = center, v = i;
                if (u > v) swap(u, v);
                fixed_edges.insert({u, v});
            }
        }
    }

    AdaptiveState state(n);

    while (true) {
        
        queries = current_case_queries; 
        query_limit = current_case_limit;

        string token;
        cin >> token;
        
        if (cin.eof() || token.empty()) {
            finish(_pe, "Unexpected EOF from solution");
        }

        if (token == "!") {
            int ans;
            cin >> ans;
            
            if (is_fixed) {
                if (fixed_type == ans) {
                    
                } else {
                    finish(_wa, format("Wrong answer on fixed case: expected %d, got %d", fixed_type, ans));
                }
            } else {
                
                bool possible_star = !state.star_centers.empty();
                
                bool possible_chain = state.can_be_chain(false, -1, -1); 
                
                
                

                if (ans == 1) { 
                    if (possible_star) finish(_wa, "Star was still possible");
                    if (!possible_chain) finish(_wa, "Chain was NOT possible (inconsistent state)");
                } else if (ans == 2) { 
                    if (possible_chain) finish(_wa, "Chain was still possible");
                    if (!possible_star) finish(_wa, "Star was NOT possible (inconsistent state)");
                } else {
                    finish(_pe, format("Invalid answer type: %d", ans));
                }
            }
            return; 
        } else if (token == "?") {
            int u, v;
            cin >> u >> v;
            
            if (u < 1 || u > n || v < 1 || v > n || u == v) {
                finish(_pe, format("Invalid query: %d %d", u, v));
            }
            
            current_case_queries++;
            
            if (current_case_queries > HARD_CAP) {
                finish(_pe, "Hard query limit exceeded");
            }
            
            int reply = 0;
            if (is_fixed) {
                if (u > v) swap(u, v);
                reply = fixed_edges.count({u, v}) ? 1 : 0;
            } else {
                
                
                
                
                
                
                
                bool ok0_star = state.can_be_star(false, u, v);
                bool ok0_chain = state.can_be_chain(false, u, v);
                
                if (ok0_star || ok0_chain) {
                    reply = 0;
                } else {
                    
                    reply = 1;
                }
                state.update(reply == 1, u, v);
            }
            
            cout << reply << endl;
        } else {
            finish(_pe, "Expected '?' or '!'");
        }
    }
}

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

    int t = inf.readInt();
    
    
    
    
    int next_n = inf.readInt(); 

    
    cout << t << endl;

    for (int i = 0; i < t; ++i) {
        int n = next_n;
        bool is_fixed = false;
        int fixed_type = 0;
        vector<int> fixed_data;
        
        
        cout << n << endl;

        
        if (i == t - 1) {
            
            inf.skipBlanks();
            if (!inf.eof()) {
                is_fixed = true;
                fixed_type = inf.readInt();
            }
        } else {
            
            int val = inf.readInt();
            if (val == 1 || val == 2) {
                is_fixed = true;
                fixed_type = val;
            } else {
                next_n = val; 
            }
        }

        if (is_fixed) {
            if (fixed_type == 1) { 
                for (int k = 0; k < n; ++k) fixed_data.push_back(inf.readInt());
            } else { 
                fixed_data.push_back(inf.readInt());
            }
            
            
            if (i < t - 1) {
                next_n = inf.readInt();
            }
        }

        run_case(n, is_fixed, fixed_type, fixed_data);
    }

    finish(_ok, "All cases passed");
    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");

    
    
    long long seed_val = -1;
    if (argc > 1) {
        string s = argv[1];
        bool is_num = true;
        for (char c : s) if (!isdigit(c)) is_num = false;
        if (is_num && s.length() < 18) seed_val = atoll(s.c_str());
    }

    int sum_limit = 1000;
    int min_n = 4;
    vector<int> ns;

    if (seed_val == 1) {
        
        ns.push_back(1000);
    } else if (seed_val == 2) {
        
        for (int i = 0; i < 250; ++i) ns.push_back(4);
    } else if (seed_val == 3) {
        
        vector<int> sizes = {4, 5, 8, 10, 16, 32};
        int cur = 0;
        while (cur < sum_limit) {
            int x = sizes[rnd.next(sizes.size())];
            if (cur + x <= sum_limit) {
                ns.push_back(x);
                cur += x;
            } else {
                int rem = sum_limit - cur;
                if (rem >= min_n) ns.push_back(rem);
                break;
            }
        }
    } else {
        
        int rem = sum_limit;
        while (rem >= min_n) {
            
            int type = rnd.next(100);
            int n_curr;
            
            if (type < 15) n_curr = rnd.next(min_n, min(rem, 10)); 
            else if (type < 40) n_curr = rnd.next(min_n, min(rem, 50)); 
            else if (type < 85 && rem > 50) n_curr = rnd.next(50, min(rem, 200)); 
            else n_curr = rnd.next(min_n, rem); 
            
            n_curr = max(min_n, min(rem, n_curr));
            
            
            if (rem - n_curr > 0 && rem - n_curr < min_n) {
                n_curr = rem; 
            }
            
            ns.push_back(n_curr);
            rem -= n_curr;
            
            
            if (ns.size() >= 250) {
                if (rem > 0) ns.back() += rem;
                break;
            }
        }
    }
    
    
    shuffle(ns.begin(), ns.end());

    
    println(ns.size());
    
    
    for (int n : ns) {
        if (mode == "non") {
            
            
            println(n);
            
            
            int type = rnd.next(1, 2);
            println(type);
            
            if (type == 1) {
                
                vector<int> p(n);
                iota(p.begin(), p.end(), 1);
                shuffle(p.begin(), p.end());
                println(p);
            } else {
                
                int center = rnd.next(1, n);
                println(center);
            }
        } else {
            
            
            println(n);
        }
    }

    return 0;
}