Problem: cf1994_h
Difficulty: hard
Categories: math
Time limit: 1000 ms, memory limit: 256 MB

This is an interactive problem!

Timofey is writing a competition called Capture the Flag (or CTF for short). He has one task left, which involves hacking a security system. The entire system is based on polynomial hashes$^{\text{∗}}$.

Timofey can input a string consisting of lowercase Latin letters into the system, and the system will return its polynomial hash. To hack the system, Timofey needs to find the polynomial hash parameters ($p$ and $m$) that the system uses.

Timofey doesn't have much time left, so he will only be able to make $3$ queries. Help him solve the task.

$^{\text{∗}}$ The polynomial hash of a string $s$, consisting of lowercase Latin letters of length $n$, based on $p$ and modulo $m$ is $(\mathrm{ord}(s_1) \cdot p ^ 0 + \mathrm{ord}(s_2) \cdot p ^ 1 + \mathrm{ord}(s_3) \cdot p ^ 2 + \ldots + \mathrm{ord}(s_n) \cdot p ^ {n - 1}) \bmod m$. Where $s_i$ denotes the $i$-th character of the string $s$, $\mathrm{ord}(\mathrm{chr})$ denotes the ordinal number of the character $\mathrm{chr}$ in the English alphabet, and $x \bmod m$ is the remainder of $x$ when divided by $m$.

Input

Each test consists of multiple test cases. The first line contains an integer $t$ ($1 \leq t \leq 10^3$) — the number of test cases.

It is guaranteed that the $p$ and $m$ used by the system satisfy the conditions: $26 < p \leq 50$ and $p + 1 < m \leq 2 \cdot 10^9$.

Interaction

To make a query to the system, output ? $s$, where $s$ is a string of no more than $50$ characters in length, the hash of which you want to know. In response to this query, you will receive the polynomial hash of the string $s$.

To output the answer, output ! $p$ $m$, where $p$ is the base of the hash, and $m$ is the modulus. After that, immediately proceed to the next test case.

You have to make not more than $3$ queries ?, otherwise you will get verdict Wrong Answer.

After outputting a query, do not forget to output a newline and flush the output buffer. Otherwise, you will receive the verdict Idleness limit exceeded. To flush the buffer, use:

* fflush(stdout) or cout.flush() in C++;
* System.out.flush() in Java;
* flush(output) in Pascal;
* stdout.flush() in Python;
* see the documentation for other languages.

Example

Input

```
1

32

28
```

Output

```
? aa

? yb

! 31 59
```

Note

Answer for the first query is $(ord(a) \cdot 31^0 + ord(a) \cdot 31^1) \mod 59 = (1 + 1 \cdot 31) \mod 59 = 32$.

Answer for the second query is $(ord(y) \cdot 31^0 + ord(b) \cdot 31^1) \mod 59 = (25 + 2 \cdot 31) \mod 59 = 28$.

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());
}

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

    
    
    int t = inf.readInt();
    
    
    query_limit = 3LL * t;

    
    log_metrics();

    
    cout << t << endl;

    
    for (int i = 0; i < t; ++i) {
        
        int p = inf.readInt();
        long long m = inf.readLong();
        
        int local_queries = 0;
        bool case_solved = false;

        
        while (!case_solved) {
            string token;
            
            if (!(cin >> token)) {
                finish(_wa, format("Unexpected EOF waiting for query or answer on case %d", i + 1));
            }

            if (token == "!") {
                
                int ans_p;
                long long ans_m;
                if (!(cin >> ans_p >> ans_m)) {
                    finish(_wa, format("Malformatted answer parameters on case %d", i + 1));
                }

                
                if (ans_p == p && ans_m == m) {
                    case_solved = true; 
                } else {
                    finish(_wa, format("Wrong answer on case %d. Expected p=%d, m=%lld; Found p=%d, m=%lld", 
                                       i + 1, p, m, ans_p, ans_m));
                }
            } else {
                
                
                string s;
                if (token == "?") {
                    if (!(cin >> s)) {
                        finish(_wa, format("Unexpected EOF reading query string on case %d", i + 1));
                    }
                } else {
                    
                    s = token;
                }

                
                queries++;
                local_queries++;
                log_metrics();

                
                if (local_queries > 3) {
                    finish(_wa, format("Query limit exceeded on case %d (max 3)", i + 1));
                }

                
                if (s.length() > 50) {
                    finish(_wa, format("Query string too long on case %d (max 50)", i + 1));
                }

                
                long long hash_val = 0;
                long long p_pow = 1;
                for (char c : s) {
                    if (c < 'a' || c > 'z') {
                        finish(_wa, format("Invalid character '%c' in query on case %d", c, i + 1));
                    }
                    int ord = c - 'a' + 1;
                    
                    
                    
                    long long term = (long long)((__int128)ord * p_pow % m);
                    hash_val = (hash_val + term) % m;
                    
                    
                    p_pow = (long long)((__int128)p_pow * p % m);
                }

                
                cout << hash_val << endl;
            }
        }
    }

    
    finish(_ok, "All test cases passed");
    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 = 0;


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 LCG {
    unsigned long long state;
    LCG(unsigned long long seed = 123456789) : state(seed) {}
    unsigned int next() {
        state = state * 6364136223846793005ULL + 1;
        return state >> 33;
    }
    long long nextSkip(long long l, long long r) { 
        if (l > r) return l;
        unsigned long long v = ((unsigned long long)next() << 32) ^ next();
        return l + (v % (r - l + 1));
    }
};

struct Case {
    int p;
    long long m;
};


long long compute_hash(const string& s, int p, long long m) {
    long long h = 0;
    long long p_pow = 1;
    for (char c : s) {
        int ord = c - 'a' + 1; 
        
        long long term = (long long)((__int128)ord * p_pow % m);
        h = (h + term) % m;
        p_pow = (long long)((__int128)p_pow * p % m);
    }
    return h;
}

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

    
    int t = inf.readInt();
    
    
    cout << t << endl;

    
    query_limit = (long long)t * 3;
    
    
    inf.skipBlanks();
    bool fixed_mode = !inf.eof();
    
    vector<Case> fixed_cases;
    if (fixed_mode) {
        for(int i = 0; i < t; ++i) {
            int p = inf.readInt();
            long long m = inf.readLong();
            fixed_cases.push_back({p, m});
        }
    }

    LCG rng(8888); 

    for (int test_idx = 0; test_idx < t; ++test_idx) {
        
        int case_queries = 0;
        vector<Case> pool;
        
        vector<pair<string, long long>> history;

        
        if (fixed_mode) {
            pool.push_back(fixed_cases[test_idx]);
        } else {
            
            
            for (int p = 27; p <= 50; ++p) {
                
                for (int k = 2; k <= 8; ++k) pool.push_back({p, (long long)p + k});
                
                pool.push_back({p, 2000000000LL});
                pool.push_back({p, 1999999999LL});
                pool.push_back({p, 1000000007LL});
                
                for (int k = 0; k < 150; ++k) {
                    long long m = rng.nextSkip(p + 2, 2000000000LL);
                    pool.push_back({p, m});
                }
            }
        }

        while (true) {
            string type;
            if (!(cin >> type)) {
                finish(_wa, "Unexpected end of stream from contestant");
            }

            if (type == "!") {
                int ans_p;
                long long ans_m;
                if (!(cin >> ans_p >> ans_m)) {
                    finish(_wa, "Malformed answer parameters");
                }
                
                
                if (ans_p <= 26 || ans_p > 50) finish(_wa, "p out of range [27, 50]");
                if (ans_m <= ans_p + 1 || ans_m > 2000000000LL) finish(_wa, "m out of range (p+1, 2e9]");

                
                for (auto& h : history) {
                    long long expected = compute_hash(h.first, ans_p, ans_m);
                    if (expected != h.second) {
                        finish(_wa, "Answer inconsistent with previous query responses");
                    }
                }
                
                
                break; 
            } else if (type == "?") {
                string s;
                cin >> s;
                
                
                if (s.empty() || s.length() > 50) finish(_wa, "Invalid string length (must be 1..50)");
                for (char c : s) {
                    if (c < 'a' || c > 'z') finish(_wa, "Invalid characters in string (must be lowercase latin)");
                }

                
                case_queries++;
                queries_count++;
                if (case_queries > 3) {
                    finish(_wa, "Query limit exceeded (max 3 per test case)");
                }

                long long reply = -1;

                if (pool.empty()) {
                    
                    
                    reply = 0; 
                } else {
                    
                    
                    vector<pair<long long, int>> replies; 
                    replies.reserve(pool.size());
                    for (int k = 0; k < (int)pool.size(); ++k) {
                        replies.push_back({compute_hash(s, pool[k].p, pool[k].m), k});
                    }
                    
                    
                    sort(replies.begin(), replies.end());
                    
                    
                    long long best_r = -1;
                    int max_cnt = -1;
                    
                    if (!replies.empty()) {
                        long long curr_r = replies[0].first;
                        int curr_cnt = 1;
                        best_r = curr_r;
                        max_cnt = 1;
                        
                        for (size_t k = 1; k < replies.size(); ++k) {
                            if (replies[k].first == curr_r) {
                                curr_cnt++;
                            } else {
                                if (curr_cnt > max_cnt) {
                                    max_cnt = curr_cnt;
                                    best_r = curr_r;
                                }
                                curr_r = replies[k].first;
                                curr_cnt = 1;
                            }
                        }
                        if (curr_cnt > max_cnt) {
                            max_cnt = curr_cnt;
                            best_r = curr_r;
                        }
                    }
                    
                    reply = best_r;

                    
                    vector<Case> new_pool;
                    new_pool.reserve(max_cnt);
                    
                    
                    auto it1 = lower_bound(replies.begin(), replies.end(), make_pair(reply, -1));
                    auto it2 = upper_bound(replies.begin(), replies.end(), make_pair(reply, 2147483647)); 
                    
                    for (auto it = it1; it != it2; ++it) {
                        new_pool.push_back(pool[it->second]);
                    }
                    pool = move(new_pool);
                }

                
                cout << reply << endl;
                
                
                history.push_back({s, reply});
            } else {
                finish(_wa, "Invalid query format (expected ? or !)");
            }
        }
    }

    finish(_ok, "Correct");
    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");
    
    int t = opt<int>("t", 100);

    
    
    if (mode == "adp") {
        cout << t << endl;
        return 0;
    }

    
    
    long long seed_val = 0;
    try {
        if (argc > 1) seed_val = stoll(string(argv[1]));
    } catch (...) {
        seed_val = 0;
    }

    vector<pair<int, long long>> cases;

    
    if (seed_val == 1) {
        
        cases.push_back({27, 29});             
        cases.push_back({50, 2000000000LL});   
        cases.push_back({27, 2000000000LL});   
        cases.push_back({50, 52});             
    } else if (seed_val == 2) {
        
        cases.push_back({47, 1000000007LL});   
        cases.push_back({50, 1000000009LL});   
        cases.push_back({31, 998244353LL});    
    } else if (seed_val == 3) {
        
        cases.push_back({30, 32});
        cases.push_back({50, 100});
        cases.push_back({40, 42});
    }

    
    while ((int)cases.size() < t) {
        
        int p = rnd.next(27, 50);
        long long m;
        
        
        int type = rnd.next(1, 100);
        
        if (type <= 5) {
            
            m = p + 2;
        } else if (type <= 10) {
            
            m = 2000000000LL;
        } else if (type <= 25) {
            
            m = rnd.next(2000000000LL - 10000, 2000000000LL);
        } else if (type <= 60) {
            
            
            m = rnd.next((long long)p + 2, (long long)p + 5000);
        } else {
            
            
            m = rnd.next((long long)p + 2, 2000000000LL);
        }
        
        cases.push_back({p, m});
    }

    
    shuffle(cases.begin(), cases.end());

    
    cout << t << endl;
    for (int i = 0; i < t; i++) {
        cout << cases[i].first << " " << cases[i].second << "\n";
    }

    return 0;
}