Problem: arc184_a
Difficulty: medium
Categories: greedy, search, math
Time limit: 2000 ms, memory limit: 1024 MB

### Problem Statement

This problem is **interactive**, and the **judge is adaptive**. See the notes for details.  
**Also, the parameters in the problem statement are fixed at $N=1000$, $M=10$, $Q=950$.**

There are $N$ coins numbered $1, 2, \dots, N$.  
Exactly $M$ of these coins are counterfeit.

An appraiser can, in one appraisal, determine whether two coins are of the same type or different types. Specifically:

* If the two coins are both genuine or both counterfeit, they are judged to be of the same type.
* Otherwise, they are judged to be of different types.

Identify all the counterfeit coins using at most $Q$ appraisals.

### Constraints

* $\color{red}{N = 1000}$
* $\color{red}{M = 10}$
* $\color{red}{Q = 950}$

### Interaction

This is an interactive problem.  
Initially, receive $N$, $M$, and $Q$ from Standard Input:

```
$N$ $M$ $Q$
```

Next, you can perform appraisals between $0$ and $Q$ times, inclusive, as follows.

First, by outputting to Standard Output in the following format, you indicate that you are appraising coins $x$ and $y$. (Include a newline at the end.)

```
? $x$ $y$
```

Here, $x$ and $y$ must be distinct integers between $1$ and $N$, inclusive.

In response, the judge system will reply with one of the following three responses.

```
0
```

If the response is `0`, it means that coins $x$ and $y$ are of the same type.

```
1
```

If the response is `1`, it means that coins $x$ and $y$ are of different types.

```
-1
```

If the response is `-1`, it means that the appraisal is invalid. Specifically, this response is given when at least one of the following conditions is met:

* The outputted $x$ and $y$ does not satisfy the constraints.
* The number of appraisals exceeds $Q$.

If you receive this response, your program is considered incorrect. Terminate your program immediately.

Finally, by outputting to Standard Output in the following format, you answer that coins $A_1, A_2, \dots, A_{M}$ are counterfeit. (Include a newline at the end.)

```
! $A_1$ $A_2$ $\dots$ $A_{M}$
```

Here, $A_i$ must be distinct integers between $1$ and $N$, inclusive.  
After this output, terminate your program immediately.

If any of your outputs do not meet the specified format, your program will be considered incorrect.
The judge will then respond with `-1`, so in that case, terminate your program immediately.

### Notes

* **Every time you output, include a newline at the end and flush Standard Output.** Failure to do so may result in a verdict of TLE or WA.
* After outputting your answer (or receiving `-1`), terminate your program immediately. Otherwise, the verdict is indeterminate.
* Beware that unnecessary newlines are considered as malformed.
* **The judge for this problem is adaptive.** That is, at any point, as long as consistency can be maintained, the judge may change which coins are counterfeit. See the sample interaction for details.

### Sample Interaction

In this interaction, $N=5$, $M=2$, $Q=10$, and the judge initially considers coins $1$ and $2$ to be counterfeit.

Note that this example does not meet the constraints and is not included in the judge.

| Input | Output | Explanation |
| --- | --- | --- |
| `5 2 10` |  | $N$, $M$, and $Q$ are given. |
|  | `? 1 2` | You appraise coins $1$ and $2$. |
| `0` |  | Coins $1$ and $2$ are judged to be of the same type. |
|  | `? 1 3` | You appraise coins $1$ and $3$. |
| `1` |  | Coins $1$ and $3$ are judged to be of different types. |
|  | `? 1 4` | You appraise coins $1$ and $4$. |
| `1` |  | Coins $1$ and $4$ are judged to be of different types. |
|  | `! 1 2` | You answer that coins $1$ and $2$ are counterfeit. |
|  |  | Indeed, coins $1$ and $2$ are considered counterfeit, but it is also possible to consider coins $3$ and $4$ as counterfeit while maintaining consistency. Therefore, the judge may change the counterfeit coins to $3$ and $4$. As a result, the judge may judge this answer as incorrect. |

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 MIN_HARD_CAP = 200000;
static const long long HARD_CAP_MULTIPLIER = 10;



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


#define FINISH(verdict, ...) \
    do { \
        log_metrics(); \
        quitf(verdict, __VA_ARGS__); \
    } while (0)

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

    
    
    int N = inf.readInt();
    int M = inf.readInt();
    int Q = inf.readInt();

    
    query_limit = Q;
    
    
    
    log_metrics();

    vector<bool> is_counterfeit(N + 1, false);
    set<int> expected_counterfeit;
    for (int i = 0; i < M; ++i) {
        int c = inf.readInt();
        
        if (c < 1 || c > N) {
            FINISH(_fail, "Hidden input coin index %d out of range [1, %d]", c, N);
        }
        is_counterfeit[c] = true;
        expected_counterfeit.insert(c);
    }

    
    cout << N << " " << M << " " << Q << endl;

    long long hard_cap = max(MIN_HARD_CAP, HARD_CAP_MULTIPLIER * query_limit);

    
    while (true) {
        
        if (queries > hard_cap) {
            FINISH(_pe, "Hard query limit of %lld exceeded", hard_cap);
        }

        
        if (queries > 0 && queries % 10 == 0) {
            log_metrics();
        }

        string token;
        
        if (!(cin >> token)) {
             FINISH(_wa, "Unexpected EOF while reading request token");
        }

        if (token == "!") {
            
            vector<int> ans;
            set<int> ans_set;
            for (int i = 0; i < M; ++i) {
                int val;
                if (!(cin >> val)) {
                    FINISH(_wa, "Unexpected EOF while reading answer indices");
                }
                ans.push_back(val);
                ans_set.insert(val);
            }
            
            
            for (int x : ans) {
                if (x < 1 || x > N) {
                    FINISH(_wa, "Answer index %d out of range [1, %d]", x, N);
                }
            }
            if ((int)ans_set.size() != M) {
                FINISH(_wa, "Answer must contain exactly %d distinct indices", M);
            }

            
            if (ans_set == expected_counterfeit) {
                FINISH(_ok, "Correct answer");
            } else {
                FINISH(_wa, "Incorrect set of counterfeit coins");
            }

        } else {
            
            int u, v;

            if (token == "?") {
                
                if (!(cin >> u >> v)) {
                    FINISH(_wa, "Unexpected EOF reading query arguments after '?'");
                }
            } else {
                
                
                try {
                    size_t processed = 0;
                    u = stoi(token, &processed);
                    
                    if (processed != token.size()) throw exception();
                } catch (...) {
                    FINISH(_wa, "Invalid token '%s' where integer expected", token.c_str());
                }
                
                if (!(cin >> v)) {
                    FINISH(_wa, "Unexpected EOF reading second query argument");
                }
            }

            
            if (u < 1 || u > N || v < 1 || v > N) {
                FINISH(_wa, "Query indices %d %d out of range [1, %d]", u, v, N);
            }
            if (u == v) {
                FINISH(_wa, "Query indices must be distinct: %d %d", u, v);
            }

            
            queries++;

            
            
            bool type_u = is_counterfeit[u];
            bool type_v = is_counterfeit[v];
            int result = (type_u == type_v) ? 0 : 1;

            cout << result << endl;
        }
    }

    return 0;
}


Adaptive interactor (C++):

#include "testlib.h"
#include <bits/stdc++.h>

using namespace std;




static long long queries_count = 0;
static long long query_limit = 950; 
static int N_val = 1000;
static int M_val = 10;
static const int HARD_CAP = 200000;




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

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




struct DSU {
    vector<int> parent;
    vector<int> edge_weight; 
    
    
    vector<int> cnt0;
    vector<int> cnt1;

    void init(int n) {
        parent.resize(n + 1);
        iota(parent.begin(), parent.end(), 0);
        edge_weight.assign(n + 1, 0);
        cnt0.assign(n + 1, 1);
        cnt1.assign(n + 1, 0);
    }

    
    pair<int, int> find(int i) {
        if (parent[i] != i) {
            pair<int, int> root_res = find(parent[i]);
            parent[i] = root_res.first;
            edge_weight[i] = (edge_weight[i] + root_res.second) % 2;
        }
        return {parent[i], edge_weight[i]};
    }

    
    
    void unite(int u, int v, int w) {
        pair<int, int> root_u = find(u);
        pair<int, int> root_v = find(v);
        int ru = root_u.first;
        int rv = root_v.first;
        if (ru == rv) return; 

        int du = root_u.second;
        int dv = root_v.second;
        
        
        
        int K = (w + du + dv) % 2;

        
        parent[rv] = ru;
        edge_weight[rv] = K;

        
        
        
        
        if (K == 0) {
            cnt0[ru] += cnt0[rv];
            cnt1[ru] += cnt1[rv];
        } else {
            cnt0[ru] += cnt1[rv];
            cnt1[ru] += cnt0[rv];
        }
    }
} dsu;




using CountT = double; 





CountT solve_ways(int merge_u = -1, int merge_v = -1, int merge_w = -1) {
    
    
    
    vector<pair<int, int>> comps;
    comps.reserve(N_val);

    if (merge_u != -1) {
        int ru = merge_u;
        int rv = merge_v;
        int K = merge_w;
        
        int c0, c1; 
        if (K == 0) {
            
            
            
            c0 = dsu.cnt0[ru] + dsu.cnt0[rv];
            c1 = dsu.cnt1[ru] + dsu.cnt1[rv];
        } else {
            
            
            
            c0 = dsu.cnt0[ru] + dsu.cnt1[rv];
            c1 = dsu.cnt1[ru] + dsu.cnt0[rv];
        }
        comps.push_back({c1, c0}); 
    }

    for (int i = 1; i <= N_val; i++) {
        if (dsu.parent[i] == i) {
            if (i == merge_u || i == merge_v) continue;
            comps.push_back({dsu.cnt1[i], dsu.cnt0[i]});
        }
    }

    
    vector<CountT> dp(M_val + 1, 0.0);
    dp[0] = 1.0;

    for (auto& p : comps) {
        int w_g = p.first;  
        int w_c = p.second; 
        
        vector<CountT> next_dp(M_val + 1, 0.0);
        bool possible = false;
        
        for (int k = 0; k <= M_val; k++) {
            if (dp[k] > 1e-9) { 
                
                if (k + w_g <= M_val) {
                    next_dp[k + w_g] += dp[k];
                    possible = true;
                }
                
                if (k + w_c <= M_val) {
                    next_dp[k + w_c] += dp[k];
                    possible = true;
                }
            }
        }
        dp = next_dp;
        if (!possible) return 0.0;
    }

    return dp[M_val];
}




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

    
    if (!inf.eof()) {
        N_val = inf.readInt();
        M_val = inf.readInt();
        query_limit = inf.readInt();
    }
    
    
    dsu.init(N_val);

    
    cout << N_val << " " << M_val << " " << query_limit << endl;

    string token;
    while (cin >> token) {
        if (token == "?") {
            int u, v;
            if (!(cin >> u >> v)) finish(_pe, "invalid query params");
            
            queries_count++;
            if (queries_count > HARD_CAP) finish(_pe, "hard cap exceeded");

            if (u < 1 || u > N_val || v < 1 || v > N_val || u == v) {
                finish(_wa, "invalid query indices");
            }
            
            if (queries_count > query_limit) {
                
                cout << -1 << endl;
                
                finish(_ok, "query limit exceeded"); 
            }

            pair<int, int> r_u = dsu.find(u);
            pair<int, int> r_v = dsu.find(v);

            int ans = 0;

            if (r_u.first == r_v.first) {
                
                ans = (r_u.second + r_v.second) % 2;
            } else {
                
                
                
                int du = r_u.second;
                int dv = r_v.second;
                int K0 = (0 + du + dv) % 2;
                int K1 = (1 + du + dv) % 2;

                CountT w0 = solve_ways(r_u.first, r_v.first, K0);
                CountT w1 = solve_ways(r_u.first, r_v.first, K1);

                
                if (w0 < 0.5 && w1 < 0.5) {
                    
                    ans = 0; 
                } else if (w0 >= w1) {
                    ans = 0;
                } else {
                    ans = 1;
                }
                
                dsu.unite(u, v, ans);
            }

            cout << ans << endl;

        } else if (token == "!") {
            vector<int> A;
            for (int i = 0; i < M_val; i++) {
                int x;
                if (!(cin >> x)) finish(_pe, "invalid answer format");
                A.push_back(x);
            }
            
            sort(A.begin(), A.end());
            if (unique(A.begin(), A.end()) != A.end()) finish(_wa, "duplicate coins");
            if (!A.empty() && (A[0] < 1 || A.back() > N_val)) finish(_wa, "coins out of bounds");

            
            vector<bool> is_c(N_val + 1, false);
            for (int x : A) is_c[x] = true;

            
            vector<int> root_type(N_val + 1, -1); 
            bool consistent = true;

            for (int i = 1; i <= N_val; i++) {
                auto res = dsu.find(i);
                int r = res.first;
                int d = res.second;
                int type_i = is_c[i] ? 1 : 0;
                
                
                int inferred_r = type_i ^ d;
                
                if (root_type[r] == -1) {
                    root_type[r] = inferred_r;
                } else {
                    if (root_type[r] != inferred_r) {
                        consistent = false;
                        break;
                    }
                }
            }

            if (!consistent) finish(_wa, "answer inconsistent with queries");

            
            
            
            CountT total = solve_ways();
            if (total > 1.5) {
                finish(_wa, "solution not unique");
            }

            finish(_ok, "correct");

        } else {
            finish(_pe, "unknown token");
        }
    }

    
    finish(_pe, "unexpected exit");
    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");

    
    const int N = 1000;
    const int M = 10;
    const int Q = 950;

    
    println(N, M, Q);

    
    auto generate_random = [&]() {
        vector<int> p(N);
        iota(p.begin(), p.end(), 1);
        shuffle(p.begin(), p.end()); 
        vector<int> res;
        for (int i = 0; i < M; ++i) res.push_back(p[i]);
        sort(res.begin(), res.end());
        return res;
    };

    vector<int> counterfeit;

    if (mode == "non") {
        
        long long seed_val = atoll(argv[1]);

        if (seed_val == 1) {
            
            for (int i = 1; i <= M; ++i) 
                counterfeit.push_back(i);
        } else if (seed_val == 2) {
            
            for (int i = 0; i < M; ++i) 
                counterfeit.push_back(N - i);
            sort(counterfeit.begin(), counterfeit.end());
        } else if (seed_val == 3) {
            
            
            for (int i = 0; i < M; ++i) 
                counterfeit.push_back(1 + 2 * i);
        } else {
            
            counterfeit = generate_random();
        }
    } else {
        
        
        
        
        
        counterfeit = generate_random();
    }

    
    println(counterfeit);

    return 0;
}