Problem: icpc2022_northern_eurasia_finals_i
Difficulty: medium
Categories: data structures, search, math
Time limit: 3000 ms, memory limit: 1024 MB

Oh no, this wicked jury hides something from you again, and you need to guess it interactively.

This time, you need to find an integer $n$ . To do that, you can make at most 10 queries of the form “What is the $k$ -th decimal digit of the product of all integers from 1 to $n$ (also known as factorial and denoted as $n .$ !)?”.

# Interaction Protocol

In the first line, there is an integer $t$ ( $1 \leq t \leq 1 0 0$ ) — the number of tests you shall process.

For each test, the integer $n$ is chosen in advance. The length of $n !$ is at most $2 0 0 0 0$ , so $1 \leq n \leq 5 9 8 2$ .

You can make at most 10 queries of the form $" ? ~ k "$ ( $0 \leq k < 2 0 0 0 0$ ). In response to the query, you will get a single digit — the $k$ -th decimal digit of $n$ ! (the response is between 0 and 9 inclusive). Digits are numbered from 0, starting with the least significant digit. If $n$ ! is too short, and there is no $k$ -th digit, then 0 is returned.

After your program finds the value of $n$ it shall answer with “! $n$ ”. If the answer is correct, then you will receive “YES” and should proceed to the next test or terminate if it was the last one. If the answer is not correct, or you are trying to guess, and there are several possible answers consistent with the information you have received, you will get “NO”. In that case, your submission will receive “Wrong answer” verdict and your code shall terminate immediately.

# Example

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>2</td><td>？0</td></tr><tr><td>1</td><td></td></tr><tr><td>YES</td><td>！1</td></tr><tr><td>0</td><td>？0</td></tr><tr><td>2</td><td>？ 19997</td></tr><tr><td></td><td>！5982</td></tr><tr><td>YES</td><td></td></tr></table>

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

using namespace std;

static constexpr int MAX_N = 5982;
static constexpr int MAX_K = 20000;
static constexpr int QUERY_LIMIT_PER_TEST = 10;

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 (...) {}
}

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


static vector<array<uint8_t, MAX_K>> factorial_digits;

static void precompute_factorial_digits() {
    factorial_digits.assign(MAX_N + 1, {});

    static constexpr int BASE = 10000; 
    vector<int> cur = {1};             

    for (int n = 1; n <= MAX_N; ++n) {
        long long carry = 0;
        for (size_t i = 0; i < cur.size(); ++i) {
            long long val = 1LL * cur[i] * n + carry;
            cur[i] = static_cast<int>(val % BASE);
            carry = val / BASE;
        }
        while (carry > 0) {
            cur.push_back(static_cast<int>(carry % BASE));
            carry /= BASE;
        }

        auto& out = factorial_digits[n];

        for (size_t i = 0; i + 1 < cur.size(); ++i) {
            int v = cur[i];
            for (int d = 0; d < 4; ++d) {
                int pos = static_cast<int>(i) * 4 + d;
                if (pos >= MAX_K) break;
                out[pos] = static_cast<uint8_t>(v % 10);
                v /= 10;
            }
        }

        int v = cur.back();
        for (int d = 0; d < 4; ++d) {
            int pos = static_cast<int>(cur.size() - 1) * 4 + d;
            if (pos >= MAX_K) break;
            out[pos] = static_cast<uint8_t>(v % 10);
            v /= 10;
            if (v == 0) break;
        }
    }
}

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

    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    precompute_factorial_digits();

    int t = inf.readInt(1, 100, "t");
    vector<int> ns(t);
    for (int i = 0; i < t; ++i) ns[i] = inf.readInt(1, MAX_N, format("n[%d]", i + 1).c_str());

    query_limit = 1LL * QUERY_LIMIT_PER_TEST * t;
    log_metrics();

    cout << t << endl;

    for (int tc = 0; tc < t; ++tc) {
        const int hidden_n = ns[tc];

        int queries_this_test = 0;
        vector<char> possible(MAX_N + 1, 1);
        possible[0] = 0;
        int possible_count = MAX_N;

        while (true) {
            string tok;
            if (!(cin >> tok)) finish(_pe, "Unexpected EOF while reading operation");

            if (!tok.empty() && tok[0] == '?') {
                int k = INT_MIN;
                if (tok.size() == 1) {
                    if (!(cin >> k)) finish(_pe, "Expected k after '?'");
                } else {
                    try {
                        k = stoi(tok.substr(1));
                    } catch (...) {
                        finish(_pe, "Invalid query token");
                    }
                }

                if (queries_this_test >= QUERY_LIMIT_PER_TEST) {
                    cout << "NO" << endl;
                    finish(_wa, "Query limit exceeded");
                }
                if (k < 0 || k >= MAX_K) {
                    cout << "NO" << endl;
                    finish(_wa, "k out of range");
                }

                const int d = static_cast<int>(factorial_digits[hidden_n][k]);
                cout << d << endl;

                for (int cand = 1; cand <= MAX_N; ++cand) {
                    if (!possible[cand]) continue;
                    if (static_cast<int>(factorial_digits[cand][k]) != d) {
                        possible[cand] = 0;
                        --possible_count;
                    }
                }

                ++queries_this_test;
                ++queries;
                log_metrics();
                continue;
            }

            if (!tok.empty() && tok[0] == '!') {
                int guess = INT_MIN;
                if (tok.size() == 1) {
                    if (!(cin >> guess)) finish(_pe, "Expected n after '!'");
                } else {
                    try {
                        guess = stoi(tok.substr(1));
                    } catch (...) {
                        finish(_pe, "Invalid answer token");
                    }
                }

                if (guess != hidden_n) {
                    cout << "NO" << endl;
                    finish(_wa, "Wrong answer");
                }
                if (possible_count != 1 || !possible[hidden_n]) {
                    cout << "NO" << endl;
                    finish(_wa, "Ambiguous answer");
                }

                cout << "YES" << endl;
                break;
            }

            finish(_pe, "Expected '?' or '!'");
        }
    }

    finish(_ok, "All tests solved");
}


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 MAX_N = 5982;

    if (mode == "adp") {
        
        
        
        
        int t = 100;
        println(t);
        for (int i = 0; i < t; i++) {
            println(1, MAX_N);
        }
        return 0;
    }

    
    
    
    
    
    int seed_val = atoi(argv[1]);
    vector<int> ns;

    if (seed_val == 1) {
        
        ns = {1, 2, 3, 4, 5, 9, 10, 19, 20, 50, 
              100, 500, 1000, 2000, 3000, 4000, 5000, 
              MAX_N - 2, MAX_N - 1, MAX_N};
    } else if (seed_val == 2) {
        
        for (int k = 1; k <= MAX_N; k *= 2) ns.push_back(k);
        for (int k = 1; k <= MAX_N; k *= 10) ns.push_back(k);
        
        vector<int> extra;
        for (int x : ns) {
            if (x > 1) extra.push_back(x - 1);
            if (x < MAX_N) extra.push_back(x + 1);
        }
        ns.insert(ns.end(), extra.begin(), extra.end());
        
        
        sort(ns.begin(), ns.end());
        ns.erase(unique(ns.begin(), ns.end()), ns.end());
    } else if (seed_val == 3) {
        
        for (int k = 1; k <= MAX_N; k += 59) {
            ns.push_back(k);
        }
        if (ns.back() != MAX_N) ns.push_back(MAX_N);
    } else {
        
        int t = 100;
        for (int i = 0; i < t; i++) {
            ns.push_back(rnd.next(1, MAX_N));
        }
    }

    
    if (ns.size() > 100) {
        shuffle(ns.begin(), ns.end());
        ns.resize(100);
    } else {
        
        shuffle(ns.begin(), ns.end());
    }
    
    
    if (ns.empty()) ns.push_back(MAX_N);

    
    println(ns.size());
    for (int x : ns) {
        println(x);
    }

    return 0;
}