Problem: icpc2023_asia_macau_k
Difficulty: medium
Categories: bit
Time limit: 2000 ms, memory limit: 1024 MB

# This is an interactive problem.

In the simplified version of the game “Understand,” you find yourself in a $2 5 6 \times 2 5 6$ grid. There are $n$ items within this grid whose precise positions are yet unknown. It is possible that multiple items are in the same grid cell.

Your task is to determine the locations of each item through a series of interactive queries, with a limit of no more than 16 queries. Each query allows you to draw a simple path on the grid, and the interactor will respond with a binary string of length $n$ , representing whether each item is on that path.

It is guaranteed that the interactor is non-adaptive, meaning the positions of the items are already determined before you start querying.

# Input

The first line of input contains an integer $n$ ( $1 \leq n \leq 1 0 0 0 0$ ), denoting the number of items in the grid.

# Interaction Protocol

The interaction begins after reading the integer $n$ .

Then, make at most 16 queries. To make a query: output “? $x _ { 0 } \ y _ { 0 } \ k \ s ^ { \prime }$ in a line. Here, $1 \leq x _ { 0 } , y _ { 0 } \leq 2 5 6$ represents that the path starts at the $x _ { 0 }$ -th row from top to bottom and the $y _ { 0 }$ -th column from left to right. $1 \leq k \leq 2 5 6 \times 2 5 6$ represents the length of the simple path. $s$ is a string of length $k - 1$ consisting of characters ‘L,’ ‘R,’‘ U’, and ‘D.’ The $i$ -th character represents the direction of movement from the $i$ -th cell to the $( i + 1 )$ -th cell along the path, where

• ‘L’ means moving from $( x , y )$ to $( x , y - 1 )$ , • ‘R’ means moving from $( x , y )$ to $( x , y + 1 )$ , • ‘U’ means moving from $( x , y )$ to $( x - 1 , y )$ , • ‘D’ means moving from $( x , y )$ to $( x + 1 , y )$ .

If $k = 1$ , you can simply output “? $x _ { 0 } \ y _ { 0 } \ k ^ { \prime }$ and may not output the additional blank space (It would be OK doing so, though).

Here, the path you output must be simple, meaning your path should not pass the same grid more than once. Also, the path must lie in the $2 5 6 \times 2 5 6$ grid, meaning each grid $( x , y )$ in the path must satisfy $1 \leq x , y \leq 2 5 6$ .

After you output the query in the correct format, read in a binary string $t$ of length $n$ , where the $i$ -th $1 \leq i \leq n$ ) character of $t$ is $^ 1$ if the $i$ -th item is on your path, and the $i$ -th $\ b { 1 } \leq \ b { i } \leq \ b { n }$ ) character of $t$ is 0 otherwise. If your query is invalid or you have made more than 16 queries, you will receive a single character ‘ $\_ ^ { 7 }$ instead. Upon reading it, your program must terminate immediately to receive a “Wrong Answer” verdict, or you could get any possible verdict.

At any point of the interaction, if you want to guess the locations of each item, output “ ! $x _ { 1 } ~ y _ { 1 } ~ x _ { 2 } ~ y _ { 2 } \cdot \cdot \cdot { x _ { n } }$ $y _ { n } ^ { \dag }$ in a line, where $( x _ { i } , y _ { i } )$ represents the location of the $i$ -th item in your guess. This guess does not count as a query. After doing that, you should terminate your program immediately.

After printing each query and the answer, do not forget to output the end of the line and flush the output. Otherwise, you may not get the correct verdict. To flush the output, use:

• fflush(stdout) or cout.flush() in C++;   
• System.out.flush() in Java;   
• stdout.flush() in Python;

# Example

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>4</td><td>？ 31 9 RRRUULLD</td></tr><tr><td>0101</td><td></td></tr><tr><td>1111</td><td>？214 RRR</td></tr><tr><td>1100</td><td>？ 2 1 6 URDDR</td></tr><tr><td></td><td>？241</td></tr><tr><td>0001</td><td>!21222324</td></tr></table>

# Note

Note that additional empty lines in the sample test are for better understanding, and you should not output any additional empty lines in your program.

Here, we provide graphical illustrations of the interaction in the sample test. In the picture below, we only show the upper-left $4 \times 4$ part of the board, but the actual size of the board is $2 5 6 \times 2 5 6$ . The locations of all four items are labeled with stars. The starting point of each query path is labeled with a square, and the result is shown below the path (filled circles represent 1, and unfilled circles represent $0$ ).

![](images/985d53d6ed28714786240457fb9e5635f5f0c80853739330fd8925c3bd5fda40.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 = 16;
static bool finished = false;


static int visited_grid[257][257];

struct Point {
    int r, c;
    bool operator==(const Point& other) const {
        return r == other.r && c == other.c;
    }
};

static void log_metrics() {
    
    
    if (!finished) {
        tout << "queries=" << queries << endl;
        tout << "query_limit=" << query_limit << endl;
    }
}

static void finish(TResult verdict, const string& msg) {
    
    tout << "queries=" << queries << endl;
    tout << "query_limit=" << query_limit << endl;
    finished = true;
    quitf(verdict, "%s", msg.c_str());
}

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

    
    int n = inf.readInt(1, 10000, "n");
    vector<Point> items(n);
    for (int i = 0; i < n; i++) {
        items[i].r = inf.readInt(1, 256, "r_i");
        items[i].c = inf.readInt(1, 256, "c_i");
    }

    
    cout << n << endl;

    
    int hard_cap = 2000; 

    while (true) {
        
        log_metrics();

        if (queries > hard_cap) {
            finish(_pe, "Hard query limit exceeded (infinite loop?)");
        }

        string token;
        if (!(cin >> token)) {
            
            break; 
        }

        if (token == "!") {
            
            vector<Point> guess(n);
            for (int i = 0; i < n; i++) {
                if (!(cin >> guess[i].r >> guess[i].c)) {
                    finish(_wa, "Failed to read final answer coordinates");
                }
            }

            
            bool ok = true;
            for (int i = 0; i < n; i++) {
                if (!(guess[i] == items[i])) {
                    ok = false;
                    break;
                }
            }

            if (ok) {
                finish(_ok, "Correct guess");
            } else {
                finish(_wa, "Incorrect items");
            }
        }

        
        int x0, y0, k;
        if (token == "?") {
            if (!(cin >> x0 >> y0 >> k)) finish(_wa, "Invalid query format");
        } else {
            
            try {
                x0 = stoi(token);
            } catch (...) {
                finish(_wa, "Invalid token (expected integer, ?, or !)");
            }
            if (!(cin >> y0 >> k)) finish(_wa, "Invalid query format");
        }

        
        if (x0 < 1 || x0 > 256 || y0 < 1 || y0 > 256) {
            cout << "_" << endl;
            finish(_wa, "Starting coordinates out of bounds");
        }
        if (k < 1 || k > 65536) { 
            cout << "_" << endl;
            finish(_wa, "Path length k out of valid range");
        }

        string s = "";
        if (k > 1) {
            if (!(cin >> s)) finish(_wa, "Failed to read path string");
        }

        
        if (queries >= query_limit) {
            cout << "_" << endl;
            finish(_wa, "Query limit exceeded");
        }

        if ((int)s.length() != k - 1) {
            cout << "_" << endl;
            finish(_wa, "String length mismatch with k");
        }

        
        vector<Point> path;
        path.reserve(k);
        path.push_back({x0, y0});
        
        int curr_r = x0;
        int curr_c = y0;
        bool invalid_move = false;

        for (char c : s) {
            if (c == 'L') curr_c--;
            else if (c == 'R') curr_c++;
            else if (c == 'U') curr_r--;
            else if (c == 'D') curr_r++;
            else { invalid_move = true; break; }

            if (curr_r < 1 || curr_r > 256 || curr_c < 1 || curr_c > 256) {
                invalid_move = true; 
                break;
            }
            path.push_back({curr_r, curr_c});
        }

        if (invalid_move) {
            cout << "_" << endl;
            finish(_wa, "Invalid move or path out of bounds");
        }

        
        vector<Point> sorted_path = path;
        sort(sorted_path.begin(), sorted_path.end(), [](const Point& a, const Point& b) {
            return a.r != b.r ? a.r < b.r : a.c < b.c;
        });

        bool simple = true;
        for (size_t i = 1; i < sorted_path.size(); i++) {
            if (sorted_path[i] == sorted_path[i-1]) {
                simple = false;
                break;
            }
        }

        if (!simple) {
            cout << "_" << endl;
            finish(_wa, "Path is not simple (visited same cell twice)");
        }

        
        
        int q_id = (int)queries + 1;
        for (const auto& p : path) {
            visited_grid[p.r][p.c] = q_id;
        }

        string resp = "";
        resp.reserve(n);
        for (int i = 0; i < n; i++) {
            if (visited_grid[items[i].r][items[i].c] == q_id) {
                resp += '1';
            } else {
                resp += '0';
            }
        }

        cout << resp << endl;
        queries++;
    }

    finish(_wa, "Unexpected termination");
}


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 n = opt<int>("n", -1);
    
    
    string seed_str = (argc > 1 ? argv[1] : "");

    
    if (n == -1) {
        if (seed_str == "1") n = 4;
        else if (seed_str == "2") n = 1;
        else if (seed_str == "3") n = 10000;
        else if (seed_str == "4") n = 10000;
        else if (seed_str == "5") n = 10000;
        else n = rnd.next(1, 10000);
    }
    
    
    n = max(1, min(n, 10000));

    int ROWS = 256;
    int COLS = 256;
    vector<pair<int, int>> points(n);

    
    
    
    
    
    
    int strategy = -1;

    if (seed_str == "1") {
        strategy = 0; 
    } else if (seed_str == "2") {
        points[0] = {1, 1};
        strategy = -1; 
    } else if (seed_str == "3") {
        
        for(auto& p : points) p = {ROWS, COLS};
        strategy = -1; 
    } else if (seed_str == "4") {
        strategy = 1; 
    } else if (seed_str == "5") {
        strategy = 3; 
    } else if (seed_str == "6") {
        strategy = 2; 
    } else if (seed_str == "7") {
        strategy = 4; 
    } else {
        
        int r = rnd.next(100);
        if (r < 50) strategy = 0;      
        else if (r < 70) strategy = 2; 
        else if (r < 85) strategy = 1; 
        else if (r < 95) strategy = 4; 
        else strategy = 3;             
    }

    if (strategy == 0) {
        for(int i = 0; i < n; i++) {
            points[i] = {rnd.next(1, ROWS), rnd.next(1, COLS)};
        }
    } else if (strategy == 1) {
        set<pair<int,int>> distinct_pts;
        
        while((int)distinct_pts.size() < n) {
            distinct_pts.insert({rnd.next(1, ROWS), rnd.next(1, COLS)});
        }
        vector<pair<int,int>> v(distinct_pts.begin(), distinct_pts.end());
        shuffle(v.begin(), v.end());
        points = v;
    } else if (strategy == 2) {
        int num_centers = rnd.next(1, 5);
        vector<pair<int,int>> centers(num_centers);
        for(int i = 0; i < num_centers; i++) 
            centers[i] = {rnd.next(1, ROWS), rnd.next(1, COLS)};
        
        for(int i = 0; i < n; i++) {
            int c_idx = rnd.next(num_centers);
            int r = centers[c_idx].first + rnd.next(-15, 15);
            int c = centers[c_idx].second + rnd.next(-15, 15);
            points[i] = {max(1, min(ROWS, r)), max(1, min(COLS, c))};
        }
    } else if (strategy == 3) {
        for(int i = 0; i < n; i++) {
            int v = rnd.next(1, min(ROWS, COLS));
            points[i] = {v, v};
        }
    } else if (strategy == 4) {
        int fixed_axis = rnd.next(2); 
        int fixed_val = rnd.next(1, 256);
        for(int i = 0; i < n; i++) {
            if (fixed_axis == 0) points[i] = {fixed_val, rnd.next(1, COLS)};
            else points[i] = {rnd.next(1, ROWS), fixed_val};
        }
    }

    
    if (mode == "non") {
        cout << n << endl;
        for (int i = 0; i < n; i++) {
            cout << points[i].first << " " << points[i].second << "\n";
        }
    } else if (mode == "adp") {
        
        
        cout << n << endl;
        for (int i = 0; i < n; i++) {
            cout << points[i].first << " " << points[i].second << "\n";
        }
    }
    
    return 0;
}