Problem: icpc2020_world_finals_h
Difficulty: medium
Categories: greedy, data structures
Time limit: 10000 ms, memory limit: 256 MB

Innovative Computable Quality Control (ICQC) has developed a ground-breaking new machine for performing, well, quality control. Thanks to its novel Deep Intelligence technology, an ICQC quality control (QC) machine can automatically, with $1 0 0 \%$ accuracy, detect manufacturing errors in any machine in existence, whether it is a coffee machine, an intergalactic space ship, or a quantum computer.

ICQC is now setting up its factory for producing these QC machines. Like any other manufacturing process, some fraction of the produced machines will suffer from malfunctions and these need to be found and discarded. Fortunately, ICQC has just the product for detecting malfunctioning machines!

Obviously, ICQC should not simply use a QC machine on itself, since a malfunctioning machine might incorrectly classify itself as working correctly. Instead, ICQC will take each batch of $n$ machines produced during a day and have them test each other overnight. In particular, during every hour of the night, each of the $n$ QC machines can run a check on one of the other QC machines, and simultaneously be checked by one other QC machine.

If the machine running the check is correct, it will correctly report whether the tested machine is correct or malfunctioning, but if the machine running the check is malfunctioning, it may report either result. If a machine A is used to test a machine B multiple times it will return the same result every time, even if machine A is malfunctioning. The exact testing schedule does not have to be fixed in advance, so the choice of which machines should check which other machines during the second hour of the night may be based on the result of the tests from the first hour, and so on.

ICQC are $1 0 0 \%$ confident that strictly more than a half of the $n$ QC machines in each batch are working correctly, but the night is only 12 hours long, so there is only time to do a small number of test rounds. Can you help ICQC determine which QC machines are malfunctioning?

For example, consider Sample Interaction 1 below. After the fourth hour, every machine has tested every other machine. For machine 1, only one other machine claimed that it was malfunctioning, and if it was truly malfunctioning then at least 3 of the other machines would claim this. For machine 4, only one other machine claims that it is working, which implies that machine 2 must be malfunctioning since more than half of the machines are supposed to be working. Note that even though machine 4 is malfunctioning, it still happened to produce the correct responses in these specific test rounds.

# Interaction

The first line of input contains a single integer $b$ $( 1 \leq b \leq 5 0 0 )$ , the number of batches to follow. Each batch is independent. You should process each batch interactively, which means the input you receive will depend on the previous output of your program.

The first line of input for each batch contains a single integer $n$ $1 \leq n \leq 1 0 0 )$ , the number of QC machines in the batch. The interaction then proceeds in rounds. In each round, your program can schedule tests for the next hour, by writing a line of the form “test $x _ { 1 } x _ { 2 } \dots x _ { n } { } ^ { \prime }$ indicating that each machine $i$ should run a test on machine $x _ { i }$ . If $x _ { i } = 0$ , then machine $i$ is idle in that round and performs no test. All positive numbers in the sequence must be distinct.

After writing this line, there will be a result to read from the input. The result is one line containing a string of length $n$ , having a $\cdot _ { 1 } ,$ in position $i$ if machine $i$ says that machine $x _ { i }$ is working correctly, ‘0’ if machine $i$ says that machine $x _ { i }$ is malfunctioning, and $^ \ast - \ast$ (dash) if machine $i$ was idle in the round.

When your program has determined which machines are malfunctioning, but no later than after 12 rounds of tests, it must write a line of the form “answer $S ^ { \ast }$ where $S$ is a binary string of length $n$ , having a $\cdot _ { 1 } ,$ in position $i$ if machine $i$ is working correctly, and a $\bullet _ { 0 } ,$ if it is malfunctioning.

After writing the answer line, your program should start processing the next batch by reading its number $n$ . When all $b$ batches have been processed, the interaction ends and your program should exit.

Notes on interactive judging:

• The evaluation is non-adversarial, meaning that the result of each machine testing each other machine is chosen in advance rather than in response to your queries. • Do not forget to flush output buffers after writing. See the Addendum to Judging Notes for details. • You are provided with a command-line tool for local testing, together with input files corresponding to the sample interactions. The tool has comments at the top to explain its use.

![](images/57f6d7d3d691f15efa5bc0a1480e19c20f2589bf9755c898970f76905de255d2.jpg)

![](images/ae2797b206e33e589c778b6cf982c78ef0146b5e18eb2f643dc25602c91b0dc9.jpg)

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 void log_metrics() {
    try {
        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());
}



static void check_hard_cap() {
    if (queries > 200000) {
        finish(_pe, "Hard query limit exceeded (potential infinite loop)");
    }
}

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

    
    
    int b = inf.readInt();
    
    
    
    query_limit = (long long)b * 12;
    log_metrics(); 

    
    cout << b << endl;

    
    for (int batch = 1; batch <= b; batch++) {
        
        int n = inf.readInt();
        string S_truth = inf.readToken(); 
        
        
        
        vector<string> matrix(n);
        for (int i = 0; i < n; i++) {
            matrix[i] = inf.readToken();
        }

        
        cout << n << endl;

        int rounds_used = 0;
        bool batch_finished = false;

        while (!batch_finished) {
            check_hard_cap();

            string cmd;
            
            if (!(cin >> cmd)) {
                finish(_wa, "Unexpected EOF while reading command");
            }

            if (cmd == "test") {
                
                rounds_used++;
                queries++;
                log_metrics(); 

                vector<int> x(n);
                vector<int> target_counts(n + 1, 0);

                
                for (int i = 0; i < n; i++) {
                    if (!(cin >> x[i])) {
                        finish(_wa, "Unexpected EOF or invalid format reading test arguments");
                    }
                }

                
                for (int i = 0; i < n; i++) {
                    int tester = i + 1; 
                    int target = x[i];  

                    if (target < 0 || target > n) {
                        finish(_wa, format("Batch %d: Machine %d targeting invalid index %d", batch, tester, target));
                    }
                    
                    if (target != 0) {
                        
                        if (target == tester) {
                            finish(_wa, format("Batch %d: Machine %d cannot test itself", batch, tester));
                        }
                        
                        target_counts[target]++;
                        if (target_counts[target] > 1) {
                            finish(_wa, format("Batch %d: Machine %d is targeted by multiple machines", batch, target));
                        }
                    }
                }

                
                string response = "";
                for (int i = 0; i < n; i++) {
                    int target = x[i];
                    if (target == 0) {
                        response += "-";
                    } else {
                        
                        
                        response += matrix[i][target - 1];
                    }
                }
                
                cout << response << endl;

            } else if (cmd == "answer") {
                string ans;
                if (!(cin >> ans)) {
                    finish(_wa, "Unexpected EOF reading answer string");
                }
                
                
                if (ans.length() != (size_t)n) {
                    finish(_wa, format("Batch %d: Answer length %d does not match n=%d", batch, (int)ans.length(), n));
                }
                for (char c : ans) {
                    if (c != '0' && c != '1') {
                        finish(_wa, "Answer must contain only '0' and '1'");
                    }
                }

                
                if (ans != S_truth) {
                    finish(_wa, format("Batch %d: Wrong answer", batch));
                }

                
                if (rounds_used > 12) {
                    finish(_wa, format("Batch %d: Round limit exceeded (used %d, max 12)", batch, rounds_used));
                }

                
                batch_finished = true;

            } else {
                finish(_wa, "Unknown command: " + cmd);
            }
        }
    }

    finish(_ok, "All batches passed");
    return 0;
}


Adaptive interactor (C++):

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 b_arg = opt<int>("b", -1);
    
    
    long long seed_val = 0;
    if (argc > 1) seed_val = atoll(argv[1]);

    int b;
    if (b_arg != -1) {
        b = b_arg;
    } else {
        
        if (seed_val == 1) b = 10;        
        else if (seed_val == 2) b = 5;    
        else if (seed_val == 3) b = 5;    
        else b = rnd.next(5, 20);         
    }
    
    
    if (b < 1) b = 1;
    if (b > 500) b = 500;

    cout << b << endl;

    for (int k = 0; k < b; k++) {
        int n;
        if (seed_val == 1) {
            
            n = (k % 10) + 1; 
        } else if (seed_val == 2 || seed_val == 3) {
            
            n = 100;
        } else {
            
            if (rnd.next(3) == 0) n = rnd.next(1, 20);
            else n = rnd.next(20, 100);
        }

        
        int min_honest = n / 2 + 1;
        int honest_count;
        
        if (seed_val == 2) {
            
            honest_count = min_honest;
        } else {
            
            if (rnd.next(2) == 0) 
                honest_count = min_honest + rnd.next(0, max(0, (n - min_honest) / 4));
            else 
                honest_count = rnd.next(min_honest, n);
        }

        
        string S(n, '0');
        vector<int> p(n);
        iota(p.begin(), p.end(), 0);
        shuffle(p.begin(), p.end());

        for (int i = 0; i < honest_count; i++) S[p[i]] = '1';

        cout << n << endl;
        cout << S << endl;

        
        
        if (mode == "non") {
            
            
            
            
            
            
            
            int strategy = rnd.next(5);
            if (seed_val == 2) strategy = 1; 

            for (int i = 0; i < n; i++) {
                string row = "";
                if (S[i] == '1') {
                    
                    for (int j = 0; j < n; j++) row += S[j];
                } else {
                    
                    int s = (strategy == 4) ? rnd.next(4) : strategy;
                    for (int j = 0; j < n; j++) {
                        char c;
                        if (s == 0) { 
                            c = rnd.next(2) ? '1' : '0';
                        } else if (s == 1) { 
                            c = (S[j] == '1' ? '0' : '1');
                        } else if (s == 2) { 
                            c = '1';
                        } else { 
                            c = '0';
                        }
                        row += c;
                    }
                }
                cout << row << endl;
            }
        }
    }

    return 0;
}