Problem: icpc2022_moscow_regional_h
Difficulty: medium
Categories: greedy, search
Time limit: 1000 ms, memory limit: 512 MB

This is an interactive problem.

Lewis is one of the developers of the new programming language called DiverC. The main feature of the program written in this language is that the code consists of pairwise distinct words. The compiler of DiverC developed by Lewis is, of course, written in DiverC and consists of $N$ pairwise distinct words.

Lewis is using the DiverC online autofill service. But Lewis has made one serious mistake: he forgot to switch the “use my data for the improvement of the service database” function off. And Lewis was the first person who registered on this service, so now the service contains only the words from his compiler.

Hacker Fernando wants to know all the words Lewis used in the compiler. So he registered at the DiverC online autofill service (wisely switching the dangerous function off), and now, for each prefix $S$ and integer $K$ entered by Fernando, the service returns, in lexicographic order, the first $K$ words from Lewis’s code that begin with the prefix $S$ . If there are only $k < K$ words, the service gives out only $k$ words (but the service usage counter increases by $K$ even in this case).

Fernando checked the scripts used for the online service and found that one user is limited with $3 8 0 0$ as the total value of $K$ in all queries. He wants to determine all $N$ words used by Lewis with several queries such as the sum of $K$ in those queries is $3 8 0 0$ or less.

Can you help him?

# Interaction Protocol

In the beginning, your program shall read one integer $T$ — the number of the test cases to be processed $1 \leq T \leq 5$ ).

At the beginning of each test case, the jury program tells one integer $N$ — the number of the words in Lewis’s DiverC compiler ( $1 \leq N \leq 1 0 0 0$ ).

Your program can then make two types of requests:

• query S K — get $K$ ( $1 ~ \leq ~ K ~ \leq ~ N$ ) lexicographically minimal words starting with prefix $S$ $( 1 \leq | S | \leq 1 0 )$ ). If the dictionary contains only $k$ such words, where $k < K$ , the answer to the query will contain $k$ words. The response to the query will be one line of the form $k S _ { 1 } S _ { 2 } \ldots S _ { k }$ , where $k$ is the number of the words ( $0 \leq k \leq K$ ), and then $k$ words $S _ { i }$ in lexicographic order follow. • answer $S _ { 1 }$ $S _ { 2 }$ ... $S _ { N }$ — tell the full Lewis’s dictionary. After the word answer you shall print all $N$ words in an arbitrary order separated by spaces. There will be no response from the jury program to this request, and your program must then continue with the next test case or exit if the current test case was the last one.

The words in Lewis’s code are composed of lowercase English letters. The length of words is between 1 to 10 characters. All words in Lewis’s code are pairwise distinct.

The sum of $K$ for all queries of the first type for each test should be 3 800 or less.

Violating the interaction protocol or exceeding the limits for the sum of $K$ cause the “Wrong answer” verdict.

Make sure you print the newline character after each query and flush the output stream buffer (flush language command) after each request. Otherwise, the solution may get the idleness limit exceeded verdict.

Note that the jury program is adaptive, i.e. the set of Lewis’s words may be generated at the runtime, but the set is guaranteed to be consistent with the answers to previous queries.

# Example

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>1</td><td></td></tr><tr><td>4</td><td>query a 1</td></tr><tr><td>1 aaa</td><td></td></tr><tr><td>2 aaa aba</td><td>query a 4</td></tr><tr><td>1 cxyxy</td><td>query c 1</td></tr><tr><td>0</td><td>query cy 1</td></tr><tr><td></td><td>query cz 1</td></tr><tr><td>1 czzzz</td><td>answer aaa aba czzzz cxyxy</td></tr><tr><td></td><td></td></tr></table>

Non-adaptive interactor (C++):
















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

using namespace std;


static long long max_queries_seen = 0; 
static const long long QUERY_LIMIT = 3800; 
static const long long HARD_CAP = 200000;  


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


static 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 t = 1; t <= T; ++t) {
        
        int N = inf.readInt();
        
        
        vector<string> dict;
        dict.reserve(N);
        for (int i = 0; i < N; ++i) {
            dict.push_back(inf.readToken());
        }
        
        
        sort(dict.begin(), dict.end());

        
        cout << N << endl;

        long long current_case_queries = 0;
        bool case_active = true;

        while (case_active) {
            
            string type;
            
            if (!(cin >> type)) {
                finish(_wa, "Unexpected EOF (waiting for query/answer)");
            }

            
            if (type == "?") type = "query";
            if (type == "!") type = "answer";

            if (type == "query") {
                string S;
                int K;
                if (!(cin >> S >> K)) {
                    finish(_wa, "Invalid format for query command");
                }

                
                if (S.empty() || S.length() > 10) finish(_wa, "S length out of range [1, 10]");
                for (char c : S) {
                    if (c < 'a' || c > 'z') finish(_wa, "S contains non-lowercase characters");
                }
                if (K < 1 || K > N) finish(_wa, "K out of range [1, N]");

                
                
                current_case_queries += K;
                max_queries_seen = max(max_queries_seen, current_case_queries);

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

                
                
                vector<string> result;
                auto it = lower_bound(dict.begin(), dict.end(), S);
                
                while (it != dict.end() && result.size() < (size_t)K) {
                    
                    
                    if (it->size() >= S.size() && it->substr(0, S.size()) == S) {
                        result.push_back(*it);
                    } else {
                        break; 
                    }
                    it++;
                }

                
                cout << result.size();
                for (const auto& w : result) {
                    cout << " " << w;
                }
                cout << endl; 

            } else if (type == "answer") {
                
                vector<string> guess;
                guess.reserve(N);
                for (int i = 0; i < N; ++i) {
                    string w;
                    if (!(cin >> w)) finish(_wa, "Failed to read answer words");
                    guess.push_back(w);
                }

                
                
                
                sort(guess.begin(), guess.end());

                if (guess.size() != dict.size()) finish(_wa, "Incorrect number of words"); 
                
                bool correct = true;
                if (guess.size() != dict.size()) correct = false;
                else {
                    for (size_t i = 0; i < dict.size(); ++i) {
                        if (guess[i] != dict[i]) {
                            correct = false;
                            break;
                        }
                    }
                }

                if (!correct) {
                    finish(_wa, "Incorrect dictionary guessed");
                }

                
                case_active = false;

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

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


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

using namespace std;


static long long max_queries_usage = 0;
static const long long QUERY_LIMIT = 3800;


void log_metrics() {
    try {
        tout << "queries=" << max_queries_usage << 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 t = 1; t <= T; ++t) {
        
        int N = inf.readInt();
        cout << N << endl;
        
        
        cout.flush();

        
        
        
        
        string firstToken = inf.readToken();
        
        vector<string> hidden_words;
        
        if (isdigit(firstToken[0])) {
            
            long long seed_val = stoll(firstToken);
            string charset = inf.readToken();
            
            rnd.setSeed(seed_val);
            set<string> distinct_words;
            int attempts = 0;
            
            
            
            while ((int)distinct_words.size() < N && attempts < 200000) {
                attempts++;
                int strategy = rnd.next(0, 1);
                string cand = "";
                
                
                if (strategy == 0 && !distinct_words.empty()) {
                    auto it = distinct_words.begin();
                    advance(it, rnd.next((int)distinct_words.size()));
                    string base = *it;
                    if (base.length() < 10) {
                        cand = base + charset[rnd.next((int)charset.size())];
                    }
                }
                
                
                if (cand == "") {
                    int len = rnd.next(1, 10);
                    
                    if (rnd.next(5) == 0) len = rnd.next(1, 4);
                    
                    for (int i = 0; i < len; ++i) {
                        cand += charset[rnd.next((int)charset.size())];
                    }
                }
                
                distinct_words.insert(cand);
            }
            
            
            if ((int)distinct_words.size() < N) {
                string full_charset = "abcdefghijklmnopqrstuvwxyz";
                while ((int)distinct_words.size() < N) {
                    string s = "";
                    int len = rnd.next(4, 10);
                    for(int k=0; k<len; ++k) s += full_charset[rnd.next(26)];
                    distinct_words.insert(s);
                }
            }
            
            hidden_words.assign(distinct_words.begin(), distinct_words.end());
            
        } else {
            
            hidden_words.push_back(firstToken);
            for (int i = 1; i < N; ++i) {
                hidden_words.push_back(inf.readToken());
            }
        }
        
        
        sort(hidden_words.begin(), hidden_words.end());
        
        long long current_k_sum = 0;
        
        while (true) {
            string type;
            if (!(cin >> type)) {
                finish(_wa, "Unexpected EOF waiting for command");
            }
            
            if (type == "query") {
                string S;
                int K;
                if (!(cin >> S >> K)) {
                    finish(_wa, "Invalid query arguments");
                }
                
                
                if (S.length() < 1 || S.length() > 10) finish(_wa, "Prefix length must be 1-10");
                if (K < 1 || K > N) finish(_wa, "K must be between 1 and N");
                
                
                current_k_sum += K;
                max_queries_usage = max(max_queries_usage, current_k_sum);
                
                
                log_metrics();
                
                if (current_k_sum > QUERY_LIMIT) {
                    finish(_wa, "Sum of K exceeded limit (3800)");
                }
                
                
                vector<string> result;
                int count = 0;
                for (const string& w : hidden_words) {
                    
                    if (w.size() >= S.size() && w.compare(0, S.size(), S) == 0) {
                        result.push_back(w);
                        count++;
                        
                        
                        if (count >= K) break; 
                    }
                }
                
                
                cout << result.size();
                for (const string& w : result) {
                    cout << " " << w;
                }
                cout << endl;
                
            } else if (type == "answer") {
                vector<string> user_ans;
                string w;
                for (int i = 0; i < N; ++i) {
                    if (!(cin >> w)) finish(_wa, "Not enough words in answer");
                    user_ans.push_back(w);
                }
                
                
                sort(user_ans.begin(), user_ans.end());
                
                if (user_ans != hidden_words) {
                    finish(_wa, "Incorrect set of words");
                }
                
                
                break;
                
            } else {
                finish(_wa, "Unknown command: " + type);
            }
        }
    }
    
    finish(_ok, "All test cases passed");
    return 0;
}


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

using namespace std;


string gen_word(int len, const string& charset) {
    string s = "";
    for (int i = 0; i < len; ++i) {
        s += charset[rnd.next((int)charset.size())];
    }
    return s;
}

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

    
    string mode = opt<string>("mode", "non");
    
    
    
    long long seed_val = 0;
    if (argc > 1) {
        try {
            seed_val = stoll(string(argv[1]));
        } catch (...) {
            seed_val = 0;
        }
    }

    
    
    int T = 1;
    cout << T << "\n";

    
    int N;
    string charset = "abcdefghijklmnopqrstuvwxyz";
    int strategy = 0; 

    
    if (seed_val == 1) {
        
        N = 5;
        strategy = 0;
    } else if (seed_val == 2) {
        
        N = 1000;
        strategy = 0;
    } else if (seed_val == 3) {
        
        N = 1000;
        charset = "ab";
        strategy = 1;
    } else if (seed_val == 4) {
        
        N = 1000;
        charset = "abc";
        strategy = 2;
    } else {
        
        N = rnd.next(900, 1000); 
        if (rnd.next(10) == 0) N = rnd.next(1, 100); 

        int type = rnd.next(5);
        if (type == 0) charset = "abcdefghijklmnopqrstuvwxyz";
        else if (type == 1) charset = "ab";
        else if (type == 2) charset = "abcde";
        else if (type == 3) {
            
            string all = "abcdefghijklmnopqrstuvwxyz";
            shuffle(all.begin(), all.end());
            charset = all.substr(0, rnd.next(1, 10));
        } else {
            
            charset = "az";
        }

        if (charset.size() <= 2) strategy = 1;
        else if (rnd.next(3) == 0) strategy = 2;
        else strategy = 0;
    }

    
    cout << N << "\n";

    
    
    if (mode == "adp") {
        
        cout << rnd.next(1, 2000000000) << " " << charset << "\n";
        return 0;
    }

    
    
    set<string> words;
    int max_attempts = N * 500;
    int attempts = 0;

    while ((int)words.size() < N && attempts < max_attempts) {
        attempts++;
        
        if (strategy == 2) {
            
            string base = "";
            if (!words.empty() && rnd.next(3) != 0) {
                auto it = words.begin();
                std::advance(it, rnd.next((int)words.size()));
                base = *it;
            }
            if (base.length() < 10) {
                base += charset[rnd.next((int)charset.size())];
                words.insert(base);
            }
        } else {
            
            int len;
            if (charset.size() <= 2) len = rnd.next(5, 10); 
            else len = rnd.next(1, 10);
            
            
            if (rnd.next(10) == 0) len = rnd.next(1, 4);
            
            words.insert(gen_word(len, charset));
        }
    }

    
    if ((int)words.size() < N) {
        string full_charset = "abcdefghijklmnopqrstuvwxyz";
        while ((int)words.size() < N) {
            words.insert(gen_word(rnd.next(4, 10), full_charset));
        }
    }

    
    vector<string> result(words.begin(), words.end());
    shuffle(result.begin(), result.end());

    for (int i = 0; i < N; ++i) {
        cout << result[i] << (i == N - 1 ? "" : " ");
    }
    cout << "\n";

    return 0;
}