Problem: icpc2024_world_finals_l
Difficulty: hard
Categories: graph
Time limit: 5000 ms, memory limit: 256 MB

Who am I? What am I? Why am I? These are all diffcult questions that have kept philosophers reliably busy over the past millennia. But when it comes to “Where am I?",then, well modern smartphones and GPS satellites have pretty much taken the excitement out of that question.

But what if you have no GPS at hand? In one of the World Finals 2O21 problems,the Instant Cartographic Positioning Company (ICPC) demonstrated a way to determine your current location using spiral movements and observing your surroundings. Unfortunately, their method can only be used in open areas where you can move freely without any obstacles. What if you need to locate your exact position in a closed space? How can you do it? Well, now is the time to find out.

You are given a map of an area consisting of unit squares, where each square is either open or occupied by a wall. At the beginning,you are placed in one of the open unit squares,but you do not know which square it is or what direction you face. Any two individual open spaces are indistinguishable, and likewise for walls. You may walk around the area, at each step observing the distance to the next wall in the direction you face. The goal is to determine your exact position on the map.

# Interaction

The first line of input contains two integers $r$ and $c \left( 1 \leq r , c \leq 1 0 0 \right)$ specifying the size of the map. This is followed by $r$ lines, each containing $c$ characters. Each of these characters is either a dot (.） denoting an open square, or a number sign $( \# )$ denoting a square occupied by a wall.

At least one of the squares is open. You know you start in one of the open squares on the map, facing one of the four cardinal directions, but your position and direction is not given in the input. Allsquares outside the map area are considered walls.

Interaction then proceeds in rounds. In each round, one line becomes available, containing a single integer $d$ $0 \leq d \leq 9 9$ ) indicating that you see a wall in front of you at distance $d$ .This means there are exactly $d$ open squares between your square and the closest wall in the current direction. You should then output a line containing one of the following:

· le ft to turn 90 degrees to the left,   
· right to turn 90 degrees to the right,   
· step to move one square forward in your current direction,   
·yes i $j$ to claim that your current position is row $i$ ,column $j$ $\dot { \iota } \left( 1 \leq i \leq r , 1 \leq j \leq c \right)$ ， · no to claim that no mater what you do, it willnot be possible to reliably determine your position.

If you output yes or no, interaction stops and your program should terminate. Otherwise, a new interaction round begins. In order to be accepted, your solution must never step into a wall,and can run for at most lOo OoO interaction rounds (the final round where you only report yes or no counts towards this limit).

![](images/f945920d1b6f58bc78e3a9ac0d90729518e9da37862f43f498877195367acb01.jpg)

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




static long long queries = 0;
static const long long query_limit = 100000;



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 R, C;
vector<string> grid;



const int DR[] = {-1, 0, 1, 0};
const int DC[] = {0, 1, 0, -1};

struct State {
    int r, c, dir; 

    bool operator<(const State& o) const {
        if (r != o.r) return r < o.r;
        if (c != o.c) return c < o.c;
        return dir < o.dir;
    }
    bool operator==(const State& o) const {
        return r == o.r && c == o.c && dir == o.dir;
    }
};


bool is_valid(int r, int c) {
    return r >= 0 && r < R && c >= 0 && c < C && grid[r][c] == '.';
}


int get_wall_dist(const State& s) {
    int dist = 0;
    int cr = s.r;
    int cc = s.c;
    while (true) {
        int nr = cr + DR[s.dir];
        int nc = cc + DC[s.dir];
        if (!is_valid(nr, nc)) break;
        dist++;
        cr = nr;
        cc = nc;
    }
    return dist;
}



State move_state(State s, const string& action) {
    if (action == "left") {
        s.dir = (s.dir + 3) % 4;
    } else if (action == "right") {
        s.dir = (s.dir + 1) % 4;
    } else if (action == "step") {
        int nr = s.r + DR[s.dir];
        int nc = s.c + DC[s.dir];
        if (is_valid(nr, nc)) {
            s.r = nr;
            s.c = nc;
        } else {
            s.r = -1; 
        }
    }
    return s;
}


State actual_state;


vector<State> candidates;



bool are_candidates_indistinguishable() {
    if (candidates.empty()) return true;

    
    vector<State> all_states;
    map<State, int> state_to_id;
    for (int r = 0; r < R; ++r) {
        for (int c = 0; c < C; ++c) {
            if (grid[r][c] == '.') {
                for (int d = 0; d < 4; ++d) {
                    State s = {r, c, d};
                    state_to_id[s] = (int)all_states.size();
                    all_states.push_back(s);
                }
            }
        }
    }

    int N = (int)all_states.size();
    vector<int> group(N);

    
    for (int i = 0; i < N; ++i) {
        group[i] = get_wall_dist(all_states[i]);
    }

    
    
    vector<vector<int>> trans(N, vector<int>(3));
    for (int i = 0; i < N; ++i) {
        State sl = move_state(all_states[i], "left");
        trans[i][0] = state_to_id[sl];

        State sr = move_state(all_states[i], "right");
        trans[i][1] = state_to_id[sr];

        State ss = move_state(all_states[i], "step");
        if (ss.r == -1) trans[i][2] = -1; 
        else trans[i][2] = state_to_id[ss];
    }

    
    while (true) {
        vector<pair<vector<int>, int>> sigs(N);
        for (int i = 0; i < N; ++i) {
            sigs[i].second = i;
            
            sigs[i].first = {group[i]};
            for (int k = 0; k < 3; ++k) {
                int next_id = trans[i][k];
                if (next_id == -1) sigs[i].first.push_back(-1); 
                else sigs[i].first.push_back(group[next_id]);
            }
        }
        
        sort(sigs.begin(), sigs.end());

        vector<int> new_group(N);
        int g_cnt = 0;
        for (int i = 0; i < N; ++i) {
            if (i > 0 && sigs[i].first != sigs[i-1].first) {
                g_cnt++;
            }
            new_group[sigs[i].second] = g_cnt;
        }

        
        int max_g = -1;
        for (int g : group) max_g = max(max_g, g);
        if (g_cnt == max_g) break;

        group = new_group;
    }

    
    int first_group = -1;
    bool first = true;
    for (const auto& c : candidates) {
        auto it = state_to_id.find(c);
        if (it != state_to_id.end()) {
            int g = group[it->second];
            if (first) {
                first_group = g;
                first = false;
            } else {
                if (g != first_group) return false; 
            }
        }
    }
    return true;
}

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

    
    R = inf.readInt();
    C = inf.readInt();
    grid.resize(R);
    for (int i = 0; i < R; ++i) {
        grid[i] = inf.readToken();
    }
    
    int start_r = inf.readInt(); 
    int start_c = inf.readInt(); 
    int start_dir = inf.readInt(); 
    
    actual_state = {start_r - 1, start_c - 1, start_dir};

    
    for (int r = 0; r < R; ++r) {
        for (int c = 0; c < C; ++c) {
            if (grid[r][c] == '.') {
                for (int d = 0; d < 4; ++d) {
                    candidates.push_back({r, c, d});
                }
            }
        }
    }

    
    cout << R << " " << C << endl;
    for (int i = 0; i < R; ++i) {
        cout << grid[i] << endl;
    }
    cout.flush();

    
    while (true) {
        
        int d = get_wall_dist(actual_state);
        cout << d << endl; 

        
        queries++;
        if (queries > query_limit) {
            finish(_pe, "Query limit exceeded");
        }
        
        if (queries % 100 == 0) log_metrics();

        
        vector<State> next_cands;
        next_cands.reserve(candidates.size());
        for (const auto& c : candidates) {
            if (get_wall_dist(c) == d) {
                next_cands.push_back(c);
            }
        }
        candidates = next_cands;
        if (candidates.empty()) {
            finish(_wa, "System error: Valid state filtered out (logic bug or inconsistent map)");
        }

        
        string token;
        if (!(cin >> token)) {
            finish(_wa, "Unexpected EOF or invalid output format");
        }

        
        for (auto &ch : token) ch = tolower(ch);

        if (token == "left" || token == "right" || token == "step") {
            
            if (token == "step") {
                State next_s = move_state(actual_state, "step");
                if (next_s.r == -1) {
                    finish(_wa, "Stepped into a wall");
                }
                actual_state = next_s;
            } else {
                actual_state = move_state(actual_state, token);
            }

            
            vector<State> survivors;
            survivors.reserve(candidates.size());
            for (const auto& c : candidates) {
                if (token == "step") {
                    State s_next = move_state(c, "step");
                    if (s_next.r != -1) { 
                        survivors.push_back(s_next);
                    }
                } else {
                    survivors.push_back(move_state(c, token));
                }
            }
            candidates = survivors;
            
            

        } else if (token == "yes") {
            int r, c;
            if (!(cin >> r >> c)) finish(_wa, "Invalid format for 'yes' command");
            
            
            if (candidates.empty()) finish(_wa, "No valid candidates");

            bool unique_pos = true;
            for (const auto& cand : candidates) {
                if (cand.r != r - 1 || cand.c != c - 1) {
                    unique_pos = false;
                    break;
                }
            }

            if (!unique_pos) {
                finish(_wa, "Position not uniquely determined yet (ambiguous candidates remain)");
            } else {
                
                if (actual_state.r == r - 1 && actual_state.c == c - 1) {
                    finish(_ok, "Correct position");
                } else {
                    finish(_wa, "Incorrect position");
                }
            }

        } else if (token == "no") {
            
            
            
            

            
            bool position_unique = true;
            if (!candidates.empty()) {
                int r0 = candidates[0].r;
                int c0 = candidates[0].c;
                for (size_t k = 1; k < candidates.size(); ++k) {
                    if (candidates[k].r != r0 || candidates[k].c != c0) {
                        position_unique = false;
                        break;
                    }
                }
            }
            if (position_unique) {
                finish(_wa, "Position is already uniquely determined, you should answer 'yes'");
            }

            
            if (are_candidates_indistinguishable()) {
                finish(_ok, "Correctly identified indistinguishable state");
            } else {
                finish(_wa, "Positions are distinguishable; further moves can resolve ambiguity");
            }

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

    return 0;
}


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













static long long queries = 0;
static const long long query_limit = 100000;
static const long long hard_cap = 200000;


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


int R, C;
vector<string> grid_map;



const int MAX_STATES = 40005;


int dr[] = {-1, 0, 1, 0};
int dc[] = {0, 1, 0, -1};


int wall_dist[MAX_STATES];      
int next_state[MAX_STATES][3];  
int eq_class[MAX_STATES];       


vector<int> candidates;

vector<int> group_buckets[205]; 

int encode_state(int r, int c, int dir) {
    return (r * C + c) * 4 + dir;
}

void decode_state(int id, int &r, int &c, int &dir) {
    dir = id % 4;
    int tmp = id / 4;
    c = tmp % C;
    r = tmp / C;
}


int compute_dist(int r, int c, int dir) {
    int d = 0;
    while (true) {
        int nr = r + dr[dir] * (d + 1);
        int nc = c + dc[dir] * (d + 1);
        if (nr < 0 || nr >= R || nc < 0 || nc >= C || grid_map[nr][nc] == '#') {
            return d;
        }
        d++;
    }
}


void precompute() {
    vector<int> valid_ids;
    valid_ids.reserve(MAX_STATES);

    
    for (int r = 0; r < R; r++) {
        for (int c = 0; c < C; c++) {
            if (grid_map[r][c] == '.') {
                for (int dir = 0; dir < 4; dir++) {
                    int id = encode_state(r, c, dir);
                    valid_ids.push_back(id);
                    candidates.push_back(id);
                    
                    wall_dist[id] = compute_dist(r, c, dir);
                    
                    
                    next_state[id][0] = encode_state(r, c, (dir + 3) % 4);
                    
                    next_state[id][1] = encode_state(r, c, (dir + 1) % 4);
                    
                    if (wall_dist[id] > 0) {
                        next_state[id][2] = encode_state(r + dr[dir], c + dc[dir], dir);
                    } else {
                        
                        
                        next_state[id][2] = id;
                    }
                }
            }
        }
    }

    
    
    for (int id : valid_ids) eq_class[id] = wall_dist[id];

    bool changed = true;
    vector<pair<tuple<int, int, int, int>, int>> sorter;
    sorter.reserve(valid_ids.size());

    
    while (changed) {
        changed = false;
        sorter.clear();
        for (int id : valid_ids) {
            
            int c_curr = eq_class[id];
            int c_left = eq_class[next_state[id][0]];
            int c_right = eq_class[next_state[id][1]];
            int c_step = eq_class[next_state[id][2]];
            sorter.push_back({{c_curr, c_left, c_right, c_step}, id});
        }
        
        sort(sorter.begin(), sorter.end());
        
        int new_c = 0;
        for (size_t i = 0; i < sorter.size(); i++) {
            if (i > 0 && sorter[i].first != sorter[i-1].first) {
                new_c++;
            }
            int id = sorter[i].second;
            if (eq_class[id] != new_c) {
                eq_class[id] = new_c;
                changed = true;
            }
        }
    }
}

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

    
    log_metrics();

    
    R = inf.readInt();
    C = inf.readInt();
    for (int i = 0; i < R; i++) {
        string s = inf.readToken();
        grid_map.push_back(s);
    }
    
    
    cout << R << " " << C << endl;
    for (int i = 0; i < R; i++) {
        cout << grid_map[i] << endl;
    }
    cout.flush();

    
    precompute();

    
    while (true) {
        if (candidates.empty()) {
            finish(_wa, "System error: no consistent states left");
        }

        
        
        int max_d_seen = 0;
        for (int id : candidates) {
            int d = wall_dist[id];
            
            if (d >= 200) d = 200; 
            if (d < 0) d = 0;
            
            group_buckets[d].push_back(id);
            if (d > max_d_seen) max_d_seen = d;
        }

        
        int best_d = -1;
        size_t max_sz = 0;
        for (int d = 0; d <= max_d_seen; d++) {
            if (group_buckets[d].size() > max_sz) {
                max_sz = group_buckets[d].size();
                best_d = d;
            }
        }

        
        candidates.swap(group_buckets[best_d]);

        
        for (int d = 0; d <= max_d_seen; d++) {
            group_buckets[d].clear();
        }

        
        cout << best_d << endl; 

        
        string type;
        if (!(cin >> type)) {
            finish(_wa, "Unexpected EOF (solution exited without verdict)");
        }

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

        if (type == "left") {
            for (int &id : candidates) id = next_state[id][0];
        } else if (type == "right") {
            for (int &id : candidates) id = next_state[id][1];
        } else if (type == "step") {
            if (best_d == 0) {
                
                finish(_wa, "Stepped into wall");
            }
            for (int &id : candidates) id = next_state[id][2];
        } else if (type == "yes") {
            int r, c;
            if (!(cin >> r >> c)) finish(_wa, "Invalid format for yes");

            
            
            bool ok = true;
            for (int id : candidates) {
                int cr, cc, cdir;
                decode_state(id, cr, cc, cdir);
                
                if ((cr + 1) != r || (cc + 1) != c) {
                    ok = false;
                    break;
                }
            }
            
            if (ok) finish(_ok, "Correct position");
            else finish(_wa, "Position not uniquely determined or wrong guess");

        } else if (type == "no") {
            
            
            
            
            
            int c0 = eq_class[candidates[0]];
            bool distinct_classes = false;
            set<pair<int, int>> distinct_locs;
            
            for (int id : candidates) {
                if (eq_class[id] != c0) distinct_classes = true;
                int cr, cc, cdir;
                decode_state(id, cr, cc, cdir);
                distinct_locs.insert({cr, cc});
            }
            
            if (distinct_classes) {
                finish(_wa, "Position is determinable (candidates are distinguishable)");
            } else {
                if (distinct_locs.size() > 1) {
                    finish(_ok, "Correctly identified as indistinguishable");
                } else {
                    finish(_wa, "Position is uniquely determined (should say yes)");
                }
            }

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

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

    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 seed_val = atoi(argv[1]);

    int R, C;
    vector<string> grid;

    
    if (seed_val == 1) {
        
        R = 1; C = 1;
        grid.assign(R, string(C, '.'));
    } else if (seed_val == 2) {
        
        R = 100; C = 100;
        grid.assign(R, string(C, '.'));
    } else if (seed_val == 3) {
        
        
        R = 21; C = 21;
        grid.assign(R, string(C, '#'));
        
        
        for (int r = 1; r < R - 1; r += 2) {
            for (int c = 1; c < C - 1; ++c) grid[r][c] = '.';
            
            if (r + 2 < R - 1) {
                if (((r - 1) / 2) % 2 == 0) {
                    
                    grid[r + 1][C - 2] = '.';
                } else {
                    
                    grid[r + 1][1] = '.';
                }
            }
        }
    } else {
        
        int type = rnd.next(4);
        
        if (type == 0) {
            
            R = rnd.next(2, 50);
            C = rnd.next(2, 50);
            grid.assign(R, string(C, '.'));
            double density = rnd.next(0.1, 0.4);
            for (int i = 0; i < R; ++i) {
                for (int j = 0; j < C; ++j) {
                    if (rnd.next(1.0) < density) {
                        grid[i][j] = '#';
                    }
                }
            }
        } else if (type == 1) {
            
            R = rnd.next(10, 60);
            C = rnd.next(10, 60);
            grid.assign(R, string(C, '#'));
            
            int r = R / 2;
            int c = C / 2;
            int steps = R * C * 3;
            
            int dr[] = {-1, 1, 0, 0};
            int dc[] = {0, 0, -1, 1};
            
            for (int k = 0; k < steps; ++k) {
                grid[r][c] = '.';
                int d = rnd.next(4);
                int nr = r + dr[d];
                int nc = c + dc[d];
                
                if (nr >= 1 && nr < R - 1 && nc >= 1 && nc < C - 1) {
                    r = nr;
                    c = nc;
                }
            }
        } else if (type == 2) {
            
            R = rnd.next(10, 40);
            C = rnd.next(10, 40);
            grid.assign(R, string(C, '#'));
            
            int num_rooms = rnd.next(3, 8);
            for (int k = 0; k < num_rooms; ++k) {
                int h = rnd.next(3, 8);
                int w = rnd.next(3, 8);
                
                int r0 = rnd.next(1, max(1, R - h - 1));
                int c0 = rnd.next(1, max(1, C - w - 1));
                
                for (int rr = 0; rr < h; ++rr) {
                    if (r0 + rr >= R - 1) break;
                    for (int cc = 0; cc < w; ++cc) {
                        if (c0 + cc >= C - 1) break;
                        grid[r0 + rr][c0 + cc] = '.';
                    }
                }
            }
            
            int noise = R * C / 10;
            for (int k = 0; k < noise; ++k) {
                grid[rnd.next(1, R - 2)][rnd.next(1, C - 2)] = '.';
            }
        } else {
            
            R = rnd.next(5, 50);
            C = rnd.next(5, 50);
            grid.assign(R, string(C, '.'));
            int walls = rnd.next(R * C / 5);
            for (int k = 0; k < walls; ++k) {
                grid[rnd.next(R)][rnd.next(C)] = '#';
            }
        }
    }

    
    vector<pair<int, int>> opens;
    for (int i = 0; i < R; ++i) {
        for (int j = 0; j < C; ++j) {
            if (grid[i][j] == '.') {
                opens.push_back({i, j});
            }
        }
    }

    
    if (opens.empty()) {
        int r = rnd.next(R);
        int c = rnd.next(C);
        grid[r][c] = '.';
        opens.push_back({r, c});
    }

    
    cout << R << " " << C << "\n";
    for (int i = 0; i < R; ++i) {
        cout << grid[i] << "\n";
    }

    
    
    
    if (mode == "non") {
        pair<int, int> start = opens[rnd.next(opens.size())];
        
        int dir = rnd.next(4); 
        
        cout << (start.first + 1) << " " << (start.second + 1) << " " << dir << "\n";
    }

    return 0;
}