Problem: icpc2024_seerc_c
Difficulty: medium
Categories: graph, search
Time limit: 2000 ms, memory limit: 512 MB

This is an interactive problem, where your program and the judge interact via standard input and output.

In the kingdom of Duloc, Lord Farquaad is developing a network of watchtowers to monitor every corner of his land. He has a map of towers and the roads that connect them, forming an undirected simple graph $G = ( V , E )$ , where each tower is a vertex and each road is an edge between two towers. However, Farquaad is worried that some parts of Duloc might be isolated, making it impossible to reach every tower from any other.

To ensure full connectivity, he tasks you with verifying whether his network is connected. However, there’s a catch: you’re only allowed limited access to information about the graph.

You can query the network to investigate its connectivity. A query allows you to select a subset of towers $S$ and receive a count of the towers not in $S$ that have direct roads connecting them to at least one tower in $S$ . More precisely, $q u e r y ( S ) = | N ( S ) \setminus S |$ , where $S \subseteq V$ and $N ( S ) = \{ x \vert \exists y \in S$ such that $( x , y ) \in E \}$ .

Your goal is to use these queries efficiently to determine if the network is connected.

Can you help Lord Farquaad confirm the security of his kingdom by verifying that every tower is reachabl from any other in Duloc’s network?

# Input

Interaction starts by reading an integer — the number of vertices.

Then you can make queries of the type "? $\mathtt { s } ^ { \mathfrak { n } }$ (without quotes) where $s$ is a binary string of length $n$ such that character $s _ { i }$ is 1 if node $i \in S$ and 0 otherwise. After the query, read an integer — the answer to your query.

After printing a query do not forget to output end of line and flush the output. The interactor is nonadaptive. The graph does not change during the interaction.

# Constraints

You are allowed to use at most 3500 queries.

# Output

When you find if $G$ is connected or disconnected, print it in the format $" ! ~ \tt ~ x "$ (without quotes), where $x$ is 1 if $G$ is connected and 0 otherwise.

# Note

In the following interaction, $| V | = 4$ , $G = ( V , E )$ , $V = \{ 1 , 2 , 3 , 4 \}$ , $E = \{ ( 1 , 2 ) , ( 2 , 3 ) , ( 3 , 4 ) , ( 2 , 4 ) \} .$ .

<table><tr><td rowspan=1 colspan=1>Input</td><td rowspan=1 colspan=1>Output</td><td rowspan=1 colspan=1>Description</td></tr><tr><td rowspan=1 colspan=1>4</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>|V| is given.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>？1100</td><td rowspan=1 colspan=1>Ask a query for subset {1,2}.</td></tr><tr><td rowspan=1 colspan=1>2</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>The judge responds with 2.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>？ 0010</td><td rowspan=1 colspan=1>Ask a query for subset {3}.</td></tr><tr><td rowspan=1 colspan=1>2</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>The judge responds with 2.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>？1001</td><td rowspan=1 colspan=1>Ask a query for subset {1,4}.</td></tr><tr><td rowspan=1 colspan=1>2</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>The judge responds with 2.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>！1</td><td rowspan=1 colspan=1>The algorithm detected that G is connected.</td></tr></table>

Here is another example, $| V | = 2$ , $G = ( V , E )$ , $V = \{ 1 , 2 \}$ , $E = \emptyset$ .

<table><tr><td rowspan=1 colspan=1>Input</td><td rowspan=1 colspan=1>Output</td><td rowspan=1 colspan=1>Description</td></tr><tr><td rowspan=1 colspan=1>2</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>|V| is given.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>？10</td><td rowspan=1 colspan=1>Ask a query for subset {1}.</td></tr><tr><td rowspan=1 colspan=1>0</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>The judge responds with 0.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>？11</td><td rowspan=1 colspan=1>Ask a query for subset {1,2}.</td></tr><tr><td rowspan=1 colspan=1>0</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>The judge responds with 0.</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>！0</td><td rowspan=1 colspan=1>The algorithm detected that G is disconnected.</td></tr></table>

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

using namespace std;



const int MAXN = 2048;



static bitset<MAXN> adj[MAXN];
static int N, M;


static long long queries = 0;
static long long query_limit = 3500;



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


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

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

    
    
    N = inf.readInt();
    M = inf.readInt();
    
    if (N > MAXN) {
        finish(_fail, "Test case N=%d exceeds interactor capacity %d", N, MAXN);
    }
    
    
    query_limit = 3500;
    
    log_metrics();
    
    
    for (int i = 0; i < M; i++) {
        int u = inf.readInt();
        int v = inf.readInt();
        
        
        if (u < 1 || u > N || v < 1 || v > N) {
            finish(_fail, "Invalid edge (%d, %d) for N=%d", u, v, N);
        }
        
        
        u--; v--;
        
        if (u == v) continue; 
        
        adj[u].set(v);
        adj[v].set(u);
    }

    
    
    int expected_ans = 0; 
    if (N == 0) {
        expected_ans = 1; 
    } else {
        int visited_count = 0;
        vector<bool> vis(N, false);
        queue<int> q;
        
        q.push(0);
        vis[0] = true;
        visited_count++;
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            
            
            for (int v = 0; v < N; v++) {
                if (adj[u].test(v)) {
                    if (!vis[v]) {
                        vis[v] = true;
                        visited_count++;
                        q.push(v);
                    }
                }
            }
        }
        
        if (visited_count == N) expected_ans = 1;
        else expected_ans = 0;
    }

    
    
    cout << N << endl;

    
    while (true) {
        
        if (queries > query_limit + 500) {
            finish(_pe, "Hard query limit exceeded (potential infinite loop)");
        }

        string token;
        
        if (!(cin >> token)) {
            finish(_wa, "Unexpected EOF: solution finished without providing an answer");
        }

        if (token.empty()) continue;

        char type = token[0];
        
        if (type == '?') {
            string s;
            
            if (token.length() > 1) {
                s = token.substr(1);
            } else {
                if (!(cin >> s)) {
                    finish(_wa, "Unexpected EOF reading query string");
                }
            }
            
            
            if ((int)s.length() != N) {
                finish(_pe, "Invalid query string length: got %d, expected %d", (int)s.length(), N);
            }
            
            
            bitset<MAXN> mask;
            mask.reset();
            bool valid = true;
            for (int i = 0; i < N; i++) {
                if (s[i] == '1') {
                    mask.set(i);
                } else if (s[i] != '0') {
                    valid = false;
                    break;
                }
            }
            
            if (!valid) {
                finish(_pe, "Query string contains invalid characters (must be '0' or '1')");
            }

            
            bitset<MAXN> neighbors;
            neighbors.reset();
            
            
            for (int i = 0; i < N; i++) {
                if (mask.test(i)) {
                    neighbors |= adj[i];
                }
            }
            
            
            neighbors &= ~mask;
            
            int ans = (int)neighbors.count();
            
            
            cout << ans << endl;
            queries++;
            
            
            if (queries % 10 == 0) log_metrics();
        }
        else if (type == '!') {
            string ans_str;
            
            if (token.length() > 1) {
                ans_str = token.substr(1);
            } else {
                if (!(cin >> ans_str)) {
                    finish(_wa, "Unexpected EOF reading answer");
                }
            }
            
            
            int ans = -1;
            try {
                ans = stoi(ans_str);
            } catch (...) {
                finish(_pe, "Answer must be an integer");
            }
            
            
            if (ans == expected_ans) {
                finish(_ok, "Correct answer");
            } else {
                finish(_wa, "Wrong answer: expected %d, found %d", expected_ans, ans);
            }
        }
        else {
            finish(_pe, "Invalid protocol token '%s'. Expected '?' or '!'", token.c_str());
        }
    }

    return 0;
}


Adaptive interactor (C++):

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

using namespace std;







void add_connected_edges(const vector<int>& nodes, vector<pair<int, int>>& edges, double density_factor) {
    int k = nodes.size();
    if (k <= 1) return;
    
    
    
    for (int i = 1; i < k; i++) {
        
        int parent_idx = rnd.next(i);
        int u = nodes[i];
        int v = nodes[parent_idx];
        if (u > v) swap(u, v);
        edges.push_back({u, v});
    }

    
    long long max_edges = (long long)k * (k - 1) / 2;
    long long current_edges = k - 1;
    long long possible_extra = max_edges - current_edges;
    
    if (possible_extra <= 0) return;

    
    
    long long target_extra = possible_extra * density_factor;
    long long cap = (long long)k * 5; 
    if (density_factor < 0.8) { 
        target_extra = min(target_extra, cap);
    }
    
    if (target_extra <= 0) return;

    set<pair<int, int>> existing;
    for (auto& e : edges) existing.insert(e);
    
    
    
    
    
    int attempts = 0;
    while (target_extra > 0 && attempts < target_extra * 5) {
        attempts++;
        int idx1 = rnd.next(k);
        int idx2 = rnd.next(k);
        if (idx1 == idx2) continue;
        
        int u = nodes[idx1];
        int v = nodes[idx2];
        if (u > v) swap(u, v);
        
        if (existing.count({u, v})) continue;
        
        existing.insert({u, v});
        edges.push_back({u, v});
        target_extra--;
    }
}

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

    
    
    string mode = opt<string>("mode", "non");

    
    int n = opt<int>("n", -1);
    long long seed_val = atoll(argv[1]);
    string type = opt<string>("t", "random");

    
    if (n == -1) {
        if (seed_val == 1)      { n = 2; type = "disconnected"; }
        else if (seed_val == 2) { n = 2; type = "connected"; }
        else if (seed_val == 3) { n = 5; type = "line"; }
        else if (seed_val == 4) { n = 10; type = "star"; }
        else if (seed_val == 5) { n = 50; type = "isolated_1"; }     
        else if (seed_val == 6) { n = 100; type = "disconnected_half"; } 
        else if (seed_val == 7) { n = 500; type = "tree"; }
        else if (seed_val == 8) { n = 500; type = "circle"; }
        else if (seed_val == 9) { n = 1000; type = "isolated_1"; }
        else if (seed_val == 10){ n = 1000; type = "bridge"; }           
        else if (seed_val == 11){ n = 1000; type = "sparse_connected"; }
        else if (seed_val == 12){ n = 1000; type = "disconnected_many"; }
        else if (seed_val == 13){ n = 1000; type = "complete_ish"; }
        else if (seed_val == 14){ n = 1000; type = "random_disconnected"; }
        else {
            n = rnd.next(500, 1000);
            type = "random";
        }
    }

    
    if (type == "random") {
        if (rnd.next(2)) type = "connected";
        else type = "disconnected";
    }
    
    if (type == "connected") type = "sparse_connected"; 
    if (type == "disconnected") type = "random_disconnected";

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

    vector<pair<int, int>> edges;

    if (type == "line") {
        for(int i = 0; i < n - 1; i++) {
            int u = p[i], v = p[i+1];
            if (u > v) swap(u, v);
            edges.push_back({u, v});
        }
    }
    else if (type == "circle") {
        for(int i = 0; i < n; i++) {
            int u = p[i], v = p[(i+1)%n];
            if (u > v) swap(u, v);
            edges.push_back({u, v});
        }
    }
    else if (type == "star") {
        for(int i = 1; i < n; i++) {
            int u = p[0], v = p[i];
            if (u > v) swap(u, v);
            edges.push_back({u, v});
        }
    }
    else if (type == "tree") {
        add_connected_edges(p, edges, 0.0);
    }
    else if (type == "sparse_connected") {
        add_connected_edges(p, edges, 0.05);
    }
    else if (type == "complete_ish") {
        add_connected_edges(p, edges, 1.0);
    }
    else if (type == "bridge") {
        
        int mid = n / 2;
        vector<int> p1(p.begin(), p.begin() + mid);
        vector<int> p2(p.begin() + mid, p.end());
        add_connected_edges(p1, edges, 0.2);
        add_connected_edges(p2, edges, 0.2);
        
        int u = p1[rnd.next(mid)];
        int v = p2[rnd.next(n - mid)];
        if (u > v) swap(u, v);
        edges.push_back({u, v});
    }
    else if (type == "isolated_1") {
        
        if (n > 1) {
            vector<int> main_comp(p.begin(), p.end() - 1);
            add_connected_edges(main_comp, edges, 0.2);
            
        }
    }
    else if (type == "disconnected_half") {
        
        int mid = n / 2;
        vector<int> p1(p.begin(), p.begin() + mid);
        vector<int> p2(p.begin() + mid, p.end());
        add_connected_edges(p1, edges, 0.2);
        add_connected_edges(p2, edges, 0.2);
    }
    else if (type == "disconnected_many") {
        
        int cur = 0;
        while(cur < n) {
            
            int sz = rnd.next(2, max(3, n/10));
            if (cur + sz > n) sz = n - cur;
            if (sz <= 1) break; 
            
            vector<int> comp;
            for(int k=0; k<sz; k++) comp.push_back(p[cur++]);
            add_connected_edges(comp, edges, 0.5);
        }
    }
    else if (type == "random_disconnected") {
        
        int k = rnd.next(2, 5); 
        vector<vector<int>> comps(k);
        for(int x : p) {
            comps[rnd.next(k)].push_back(x);
        }
        for(auto& comp : comps) {
            if(!comp.empty()) add_connected_edges(comp, edges, 0.2);
        }
    }
    else {
        
        add_connected_edges(p, edges, 0.1);
    }

    
    
    
    cout << n << " " << edges.size() << endl;
    for (const auto& e : edges) {
        cout << e.first << " " << e.second << "\n";
    }

    return 0;
}