Problem: icpc2022_northern_eurasia_finals_h
Difficulty: hard
Categories: search, math
Time limit: 3000 ms, memory limit: 1024 MB

This is an interactive problem.

After emigrating to another country, little Hanna came across the fact that playing “Hot and cold” became more difficult: she does not understand the hint phrases! You are to write a helping program for her.

The playground where the game happens is a rectangle, and the treasure is hidden in some point with integer coordinates between 0 and $1 0 ^ { 6 }$ , inclusive. Hanna visits several points with valid coordinates. If the visited point contains the hidden treasure, she receives the phrase “Found!” in the local language. Fortunately, the intonations allow her to recognize this phrase — in this problem it will be denoted by an exclamation mark.

Otherwise, for each visited point except for the first one, Hanna receives a phrase in the local language stating whether she is now “Closer”, “Further”, or “At the same distance” from the treasure, compared to the previous point. The distances are measured in Euclidean metric. After the first visited point, in case of no treasure there, Hanna receives the phrase “Not found” in the local language.

Your program must help Hanna find the treasure by visiting at most 64 points.

# Interaction Protocol

Your program should print each visited point in a line with two coordinates between 0 and $1 0 ^ { 6 }$ , inclusive, and flush the output after each one.

For each visited point, the testing system writes one of the phrases: “Found!”, “Closer”, “Further”, “At the same distance”, or “Not found” in some language, consistent throughout the game. The first phrase ends with an exclamation mark, all other phrases do not contain exclamation marks. All phrases contain only Latin letters, spaces and exclamation marks, have lengths between 2 and 30, inclusive, don’t start or end with a space, and are pairwise distinct. The phrases are not necessarily coming from any real human language.

After receiving a phrase with an exclamation mark, your program must halt without printing anything else.

# Examples

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>Tabilmadi</td><td>500 200</td></tr><tr><td>Daha yakin</td><td>560 230</td></tr><tr><td>Daha yakin</td><td>566 240</td></tr><tr><td>Sama distanco</td><td>566 238</td></tr><tr><td>Dalej</td><td>30 239</td></tr><tr><td>Znaydeno!</td><td>566 239</td></tr><tr><td>You are lucky today!</td><td>777777777777</td></tr></table>

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

using namespace std;


long long queries = 0;
long long query_limit = 64; 
const long long HARD_CAP = 20000; 


long long Tx, Ty;       
string p_start;         
string p_closer;        
string p_further;       
string p_same;          
string p_found;         


long long prev_dist_sq = -1;
bool first_query = true;




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


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


long long distSq(long long x1, long long y1, long long x2, long long y2) {
    long long dx = x1 - x2;
    long long dy = y1 - y2;
    return dx * dx + dy * dy;
}

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

    
    
    
    
    
    
    
    
    
    
    Tx = inf.readInt();
    Ty = inf.readInt();
    
    
    
    inf.readLine(); 

    
    p_start = inf.readLine();
    p_closer = inf.readLine();
    p_further = inf.readLine();
    p_same = inf.readLine();
    p_found = inf.readLine();

    
    while (true) {
        
        if (queries > HARD_CAP) {
            finish(_pe, "Hard cap on queries exceeded");
        }

        
        string token;
        if (!(cin >> token)) {
            
            finish(_wa, "Contestant output stream ended without finding treasure");
        }

        long long x, y;
        bool parsed = false;

        
        if (token == "?" || token == "!") {
            
            if (cin >> x >> y) parsed = true;
        } else {
            
            try {
                size_t idx;
                x = stoll(token, &idx);
                
                
                if (cin >> y) parsed = true;
            } catch (...) {
                
                parsed = false;
            }
        }

        if (!parsed) {
            finish(_pe, "Expected integer coordinates, found invalid format");
        }

        
        if (x < 0 || x > 1000000 || y < 0 || y > 1000000) {
            finish(_pe, format("Point (%lld, %lld) out of valid range [0, 1000000]", x, y));
        }

        
        queries++;
        
        if (queries % 10 == 0) log_metrics();

        
        if (x == Tx && y == Ty) {
            cout << p_found << endl; 
            finish(_ok, format("Treasure found at (%lld, %lld) in %lld queries", x, y, queries));
        }

        
        long long cur_dist_sq = distSq(x, y, Tx, Ty);
        string response;

        if (first_query) {
            
            response = p_start;
        } else {
            
            if (cur_dist_sq < prev_dist_sq) {
                response = p_closer;
            } else if (cur_dist_sq > prev_dist_sq) {
                response = p_further;
            } else {
                response = p_same;
            }
        }

        
        cout << response << endl; 

        
        prev_dist_sq = cur_dist_sq;
        first_query = false;
    }

    return 0;
}


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

using namespace std;


static int queries = 0;
static const int query_limit = 64; 
static bool finished = false;


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


void finish(TResult verdict, const string& msg) {
    if (finished) return; 
    finished = true;
    log_metrics();
    quitf(verdict, "%s", msg.c_str());
}


struct Point {
    long long x, y;
};


long long distSq(Point a, Point b) {
    return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}


string PHRASE_START;   
string PHRASE_CLOSER;  
string PHRASE_FURTHER; 
string PHRASE_SAME;    
string PHRASE_FOUND;   


bool adaptive_mode = false;
Point hidden_target = {-1, -1};


vector<Point> candidates;
struct HistoryStep {
    Point q;
    int type; 
};
vector<HistoryStep> history;


bool is_consistent(Point p, const vector<HistoryStep>& hist) {
    if (hist.empty()) return true; 
    
    
    
    if (p.x == hist[0].q.x && p.y == hist[0].q.y) return false;

    for (size_t k = 1; k < hist.size(); ++k) {
        long long d_curr = distSq(p, hist[k].q);
        long long d_prev = distSq(p, hist[k-1].q);
        
        int type = hist[k].type;
        if (type == 1) { 
            if (!(d_curr < d_prev)) return false;
        } else if (type == 2) { 
            if (!(d_curr > d_prev)) return false;
        } else if (type == 3) { 
            if (!(d_curr == d_prev)) return false;
        }
        
        
        
        if (p.x == hist[k].q.x && p.y == hist[k].q.y) return false;
    }
    return true;
}


void replenish_candidates() {
    if (candidates.empty()) return;
    
    
    long long min_x = 1000000, max_x = 0;
    long long min_y = 1000000, max_y = 0;
    for (const auto& p : candidates) {
        min_x = min(min_x, p.x); max_x = max(max_x, p.x);
        min_y = min(min_y, p.y); max_y = max(max_y, p.y);
    }
    
    
    min_x = max(0LL, min_x - 10);
    max_x = min(1000000LL, max_x + 10);
    min_y = max(0LL, min_y - 10);
    max_y = min(1000000LL, max_y + 10);
    
    
    int attempts = 0;
    while (attempts < 2000 && candidates.size() < 4000) {
        attempts++;
        long long rx = rnd.next(min_x, max_x);
        long long ry = rnd.next(min_y, max_y);
        Point p = {rx, ry};
        
        
        if (is_consistent(p, history)) {
            
            
            
            candidates.push_back(p);
        }
    }
}

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

    
    
    
    
    
    
    string line = trim(inf.readLine());
    vector<string> phrases;
    
    
    if (!line.empty() && isdigit(line[0])) {
        
        adaptive_mode = false;
        stringstream ss(line);
        ss >> hidden_target.x >> hidden_target.y;
        for(int i=0; i<5; i++) phrases.push_back(trim(inf.readLine()));
    } else {
        
        adaptive_mode = true;
        phrases.push_back(line); 
        for(int i=0; i<4; i++) phrases.push_back(trim(inf.readLine())); 
    }
    
    PHRASE_START = phrases[0];
    PHRASE_CLOSER = phrases[1];
    PHRASE_FURTHER = phrases[2];
    PHRASE_SAME = phrases[3];
    PHRASE_FOUND = phrases[4];

    
    if (adaptive_mode) {
        
        candidates.push_back({0, 0});
        candidates.push_back({0, 1000000});
        candidates.push_back({1000000, 0});
        candidates.push_back({1000000, 1000000});
        for (int i = 0; i < 5000; i++) {
            candidates.push_back({rnd.next(0, 1000000), rnd.next(0, 1000000)});
        }
    }

    Point prev_q = {-1, -1};

    
    while (true) {
        
        long long qx, qy;
        if (!(cin >> qx >> qy)) {
            
            finish(_pe, "Invalid query format or unexpected EOF");
        }
        
        if (qx < 0 || qx > 1000000 || qy < 0 || qy > 1000000) {
            finish(_pe, "Coordinates out of range");
        }

        queries++;
        Point curr_q = {qx, qy};

        
        if (adaptive_mode) {
            
            if (queries == 1) {
                
                
                bool all_match = true;
                if (candidates.empty()) all_match = false; 
                for (const auto& p : candidates) {
                    if (p.x != qx || p.y != qy) {
                        all_match = false;
                        break;
                    }
                }

                if (all_match && !candidates.empty()) {
                    cout << PHRASE_FOUND << endl;
                    finish(_ok, "Found (forced on query 1)");
                } else {
                    cout << PHRASE_START << endl;
                    history.push_back({curr_q, 0});
                    
                    
                    vector<Point> next_c;
                    for (const auto& p : candidates) {
                        if (p.x != qx || p.y != qy) next_c.push_back(p);
                    }
                    candidates = next_c;
                }
            } else {
                
                
                int vote[4] = {0, 0, 0, 0}; 
                bool can_find = false;

                for (const auto& p : candidates) {
                    if (p.x == qx && p.y == qy) {
                        can_find = true;
                        continue;
                    }
                    
                    long long d_curr = distSq(p, curr_q);
                    long long d_prev = distSq(p, prev_q);
                    
                    if (d_curr < d_prev) vote[1]++;
                    else if (d_curr > d_prev) vote[2]++;
                    else vote[3]++;
                }

                
                
                
                
                int choice = 0;
                if (vote[1] == 0 && vote[2] == 0 && vote[3] == 0) {
                    if (can_find) {
                        cout << PHRASE_FOUND << endl;
                        finish(_ok, "Found (adaptive)");
                    } else {
                        
                        finish(_wa, "Inconsistent adaptive state (no candidates left)");
                    }
                } else {
                    
                    if (vote[1] >= vote[2] && vote[1] >= vote[3]) choice = 1;
                    else if (vote[2] >= vote[1] && vote[2] >= vote[3]) choice = 2;
                    else choice = 3;
                    
                    
                    if (choice == 1) cout << PHRASE_CLOSER << endl;
                    else if (choice == 2) cout << PHRASE_FURTHER << endl;
                    else cout << PHRASE_SAME << endl;
                    
                    
                    history.push_back({curr_q, choice});
                    
                    
                    vector<Point> next_c;
                    for (const auto& p : candidates) {
                        if (p.x == qx && p.y == qy) continue; 
                        
                        long long d_curr = distSq(p, curr_q);
                        long long d_prev = distSq(p, prev_q);
                        
                        bool keep = false;
                        if (choice == 1 && d_curr < d_prev) keep = true;
                        if (choice == 2 && d_curr > d_prev) keep = true;
                        if (choice == 3 && d_curr == d_prev) keep = true;
                        
                        if (keep) next_c.push_back(p);
                    }
                    candidates = next_c;
                }
            }
            
            
            if (candidates.size() < 200) {
                replenish_candidates();
            }

        } else {
            
            if (curr_q.x == hidden_target.x && curr_q.y == hidden_target.y) {
                cout << PHRASE_FOUND << endl;
                finish(_ok, "Found");
            }
            
            if (queries == 1) {
                cout << PHRASE_START << endl;
            } else {
                long long d_curr = distSq(curr_q, hidden_target);
                long long d_prev = distSq(prev_q, hidden_target);
                
                if (d_curr < d_prev) cout << PHRASE_CLOSER << endl;
                else if (d_curr > d_prev) cout << PHRASE_FURTHER << endl;
                else cout << PHRASE_SAME << endl;
            }
        }
        
        prev_q = curr_q;

        
        if (queries > 200) {
             finish(_pe, "Too many queries (hard cap exceeded)");
        }
    }
    
    return 0;
}


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

using namespace std;





















string generate_phrase(bool isFound) {
    int len = rnd.next(2, 30);
    string s = "";
    
    for (int i = 0; i < len; ++i) {
        
        if (isFound && i == len - 1) {
            s += '!';
            continue;
        }
        
        
        bool can_space = true;
        
        
        if (i == 0) can_space = false;
        
        
        
        
        if (isFound && i == len - 2) can_space = false;
        if (!isFound && i == len - 1) can_space = false;
        
        
        if (i > 0 && s.back() == ' ') can_space = false;
        
        
        if (can_space && rnd.next(6) == 0) {
            s += ' ';
        } else {
            
            int t = rnd.next(52);
            if (t < 26) s += (char)('a' + t);
            else s += (char)('A' + t - 26);
        }
    }
    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) seed_val = atoll(argv[1]);
    
    
    
    
    
    
    
    
    vector<string> phrases;
    while (phrases.size() < 5) {
        
        bool isFound = (phrases.size() == 4);
        string s = generate_phrase(isFound);
        
        
        bool distinct = true;
        for (const string &existing : phrases) {
            if (existing == s) distinct = false;
        }
        
        if (distinct) {
            phrases.push_back(s);
        }
    }
    
    
    if (mode == "non") {
        
        int MAX_COORD = 1000000;
        int x, y;
        
        
        if (seed_val == 1) {
            x = 0; y = 0;
        } else if (seed_val == 2) {
            x = MAX_COORD; y = MAX_COORD;
        } else if (seed_val == 3) {
            x = 0; y = MAX_COORD;
        } else if (seed_val == 4) {
            x = MAX_COORD; y = 0;
        } else if (seed_val == 5) {
            x = MAX_COORD / 2; y = MAX_COORD / 2;
        } else {
            
            x = rnd.next(0, MAX_COORD);
            y = rnd.next(0, MAX_COORD);
        }
        
        cout << x << " " << y << "\n";
    }
    
    
    
    for (const string &s : phrases) {
        cout << s << "\n";
    }
    
    return 0;
}