Problem: icpc2024_asia_chengdu_c
Difficulty: medium
Categories: graph, game, data structures
Time limit: 1000 ms, memory limit: 1024 MB

# This is an interactive problem.

Chinese chess is a two-player strategy board game that is very popular in China. There are 10 rows and 9 columns on a Chinese chess board. We denote the point on the $r$ -th row from bottom to top and on the $c$ -th column from left to right by $( r , c )$ ( $0 \leq r \leq 9$ , $0 \leq c \leq 8$ ).

![](images/621a04bc2ab7e1ff02c5ccc9d77b9e91209d170939652dd48d595c20c0facb85.jpg)

There are six types of pieces in this problem: kings, mandarins, rooks, knights, bishops, and pawns. Note that cannons are not considered. Additionally, all pieces can be moved anywhere on the board, which differs slightly from the original rules. The movement rules for each piece are as follows (assuming the current position is $( r , c )$ ):

<table><tr><td rowspan=1 colspan=1>Chess</td><td rowspan=1 colspan=1>Symbol</td><td rowspan=1 colspan=1>Possible positions in one move</td></tr><tr><td rowspan=1 colspan=1>King</td><td rowspan=1 colspan=1>J</td><td rowspan=1 colspan=1>(r+1,c)(r-1,c)(r,c+1)(r,c-1)</td></tr><tr><td rowspan=1 colspan=1>Mandarin</td><td rowspan=1 colspan=1>S</td><td rowspan=1 colspan=1>(r+1,c+1)(r+1,c-1)(r-1,c+1)(r-1,c-1)</td></tr><tr><td rowspan=1 colspan=1>Rook</td><td rowspan=1 colspan=1>C</td><td rowspan=1 colspan=1>all (r&#x27;,c&#x27;) such that r&#x27; = r or c = c except (r,c) itself</td></tr><tr><td rowspan=1 colspan=1>Knight</td><td rowspan=1 colspan=1>M</td><td rowspan=1 colspan=1>(r+2,c+1)(r+2,c-1)(r-2,c+1)(r-2,c-1)(r+1,c+2) (r+1,c-2)(r-1,c+2) (r-1,c-2)</td></tr><tr><td rowspan=1 colspan=1>Bishop</td><td rowspan=1 colspan=1>X</td><td rowspan=1 colspan=1>(r+2,c+2)(r+2,c-2)(r-2,c+2)(r-2,c-2)</td></tr><tr><td rowspan=1 colspan=1>Pawn</td><td rowspan=1 colspan=1>B</td><td rowspan=1 colspan=1>(r+1,c) if r≤4,otherwise (r+1,c) (r,c+1) (r,c-1)</td></tr></table>

Of course, pieces cannot move outside the board’s boundaries. Now that you understand the rules, let’s play a game.

I have placed a piece on the board, and you cannot see its position or type. However, I will provide a set $A$ of $n$ possible positions where the piece might be located. Your goal is to determine the type of the piece using the minimum number of queries. For each query, you can select a position on the board, and I will tell you the minimum number of moves required for the piece to move to that position, or I will tell you if it is impossible for the piece to reach that position.

Interesting, isn’t it? What is even more interesting is that I am actually gaming against you in secret. Truth be told, although set $A$ is fixed, I have not fixed the type and position of this piece from the start. My strategy is to adjust the answer to each of your queries in a way that maximizes the number of queries you make, without contradicting the information already given. In the case where both of our strategies are optimal, your number of queries $m$ is actually determinable. I am sure you are smart enough to figure it out. Let the game begin!

# Interaction Protocol

Firstly, you should read an integer $n$ ( $1 \leq n \leq 9 0$ ) from standard input, indicating the size of the set $A$ .

Then, read $n$ lines. Each line contains two integers $r$ and $c$ , indicating a position in the set $A$ . All positions are guaranteed to be distinct.

After reading these $n + 1$ lines, you should output an integer $m$ to standard output, indicating the number of queries. If your output $m$ is incorrect, you will get a Wrong answer verdict. You must then make exactly $m$ queries.

To make a query, output $\texttt { ? r c }$ ( $0 \leq r \leq 9 , 0 \leq c \leq 8$ ) in a single line. Then you should read an integer from standard input, indicating the minimum number of moves required for the piece to reach the selected position, or $- 1$ if the piece cannot move to that position. If your program makes an invalid query or exceeds the number of queries, the interactor will terminate immediately, and you will receive a Wrong answer verdict.

To output your guess about the type of the piece, you need to output ! ch, where ch means the symbol representing the piece that is shown in the statement.

After you make a query or output your answer, including the $m$ and the type of the piece, do not forget to output the End of Line \n and flush the output. To do this, use:

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

# Examples

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>1</td><td></td></tr><tr><td>90</td><td></td></tr><tr><td></td><td>1 ？36</td></tr><tr><td>6</td><td></td></tr><tr><td></td><td>！S</td></tr><tr><td>4</td><td></td></tr><tr><td>21</td><td></td></tr><tr><td>23</td><td></td></tr><tr><td>25</td><td></td></tr><tr><td>27</td><td></td></tr><tr><td></td><td>2 ？00</td></tr><tr><td>3</td><td></td></tr><tr><td>1</td><td>？20</td></tr></table>

# Note

For the first example, the number of moves required for each of the six types of pieces to move from position $( 9 , 0 )$ to $( 3 , 6 )$ is as follows:

<table><tr><td rowspan=1 colspan=1>King (J)</td><td rowspan=1 colspan=1>Mandarins (S)</td><td rowspan=1 colspan=1>Rooks (C)</td><td rowspan=1 colspan=1>Knights (M)</td><td rowspan=1 colspan=1>Bishops (X)</td><td rowspan=1 colspan=1>Pawns (B)</td></tr><tr><td rowspan=1 colspan=1>12</td><td rowspan=1 colspan=1>6</td><td rowspan=1 colspan=1>2</td><td rowspan=1 colspan=1>4</td><td rowspan=1 colspan=1>3</td><td rowspan=1 colspan=1>-1</td></tr></table>

Thus, you only need to make one query to determine the answer.

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


template <typename... Args>
static void finish(TResult verdict, const char* fmt, Args... args) {
    log_metrics();
    quitf(verdict, fmt, args...);
}





struct Point {
    int r, c;
};


bool isValid(int r, int c) {
    return r >= 0 && r <= 9 && c >= 0 && c <= 8;
}


int dist[10][9];


void run_bfs(char type, Point start) {
    
    for(int i=0; i<10; ++i) 
        for(int j=0; j<9; ++j) 
            dist[i][j] = -1;
    
    queue<Point> q;
    q.push(start);
    dist[start.r][start.c] = 0;
    
    while(!q.empty()){
        Point u = q.front();
        q.pop();
        int r = u.r;
        int c = u.c;
        int d = dist[r][c];
        
        vector<Point> next_moves;
        next_moves.reserve(16); 
        
        if (type == 'J') { 
            int dr[] = {1, -1, 0, 0};
            int dc[] = {0, 0, 1, -1};
            for(int k=0; k<4; ++k) next_moves.push_back({r + dr[k], c + dc[k]});
        }
        else if (type == 'S') { 
            int dr[] = {1, 1, -1, -1};
            int dc[] = {1, -1, 1, -1};
            for(int k=0; k<4; ++k) next_moves.push_back({r + dr[k], c + dc[k]});
        }
        else if (type == 'C') { 
            
            for(int nc=0; nc<9; ++nc) if(nc != c) next_moves.push_back({r, nc});
            
            for(int nr=0; nr<10; ++nr) if(nr != r) next_moves.push_back({nr, c});
        }
        else if (type == 'M') { 
            int dr[] = {2, 2, -2, -2, 1, 1, -1, -1};
            int dc[] = {1, -1, 1, -1, 2, -2, 2, -2};
            for(int k=0; k<8; ++k) next_moves.push_back({r + dr[k], c + dc[k]});
        }
        else if (type == 'X') { 
            int dr[] = {2, 2, -2, -2};
            int dc[] = {2, -2, 2, -2};
            for(int k=0; k<4; ++k) next_moves.push_back({r + dr[k], c + dc[k]});
        }
        else if (type == 'B') { 
            
            next_moves.push_back({r + 1, c});
            
            if (r > 4) {
                next_moves.push_back({r, c + 1});
                next_moves.push_back({r, c - 1});
            }
        }
        
        for(auto& v : next_moves) {
            if(isValid(v.r, v.c)) {
                if(dist[v.r][v.c] == -1) {
                    dist[v.r][v.c] = d + 1;
                    q.push(v);
                }
            }
        }
    }
}





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

    
    int n = inf.readInt();
    vector<Point> A(n);
    for(int i=0; i<n; ++i) {
        A[i].r = inf.readInt();
        A[i].c = inf.readInt();
    }
    
    
    string typeStr = inf.readToken();
    char hiddenType = typeStr[0];
    int hiddenR = inf.readInt();
    int hiddenC = inf.readInt();
    
    
    run_bfs(hiddenType, {hiddenR, hiddenC});
    
    
    cout << n << endl;
    for(const auto& p : A) {
        cout << p.r << " " << p.c << endl;
    }
    cout.flush(); 
    
    
    long long m;
    if (!(cin >> m)) {
        finish(_wa, "Failed to read m from contestant");
    }
    
    query_limit = m;
    log_metrics(); 
    
    
    int safety_cap = 200000; 
    
    while(true) {
        
        if (queries > safety_cap) {
            finish(_pe, "Hard query limit exceeded (safety cap %d)", safety_cap);
        }

        string token;
        if (!(cin >> token)) {
            finish(_wa, "Unexpected EOF while reading query/answer");
        }
        
        
        if (token == "!") {
            string ans;
            if (!(cin >> ans)) {
                finish(_wa, "Failed to read answer type after '!'");
            }
            char ansType = ans[0];
            if (ansType == hiddenType) {
                finish(_ok, "Correct guess");
            } else {
                finish(_wa, "Incorrect guess: expected %c, got %c", hiddenType, ansType);
            }
        } else if (token.size() > 1 && token[0] == '!') {
            char ansType = token[1];
            if (ansType == hiddenType) {
                finish(_ok, "Correct guess");
            } else {
                finish(_wa, "Incorrect guess: expected %c, got %c", hiddenType, ansType);
            }
        } else {
            
            int r, c;
            if (token == "?") {
                if (!(cin >> r >> c)) {
                    finish(_wa, "Failed to read coordinates after '?'");
                }
            } else {
                
                try {
                    r = stoi(token);
                } catch (...) {
                    finish(_wa, "Invalid token '%s'", token.c_str());
                }
                if (!(cin >> c)) {
                    finish(_wa, "Failed to read c after r");
                }
            }
            
            
            queries++;
            
            if (queries > query_limit) {
                finish(_wa, "Query limit exceeded (used %lld, limit %lld)", queries, query_limit);
            }
            
            
            if (!isValid(r, c)) {
                finish(_wa, "Invalid query coordinates (%d, %d)", r, c);
            }
            
            
            cout << dist[r][c] << endl;
            
        }
    }
    
    return 0;
}


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 bool metrics_logged = false;

static void log_metrics() {
    if (metrics_logged) return;
    metrics_logged = true;
    try {
        if (query_limit < 0) query_limit = 0; 
        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());
}





const int ROWS = 10;
const int COLS = 9;
const string PIECE_CHARS = "JSCMXB";

int get_piece_idx(char c) {
    auto pos = PIECE_CHARS.find(c);
    if (pos == string::npos) return -1;
    return (int)pos;
}

struct Pos {
    int r, c;
};




int dist_matrix[6][90][90];

bool valid(int r, int c) {
    return r >= 0 && r < ROWS && c >= 0 && c < COLS;
}

int get_id(int r, int c) {
    return r * COLS + c;
}

void bfs(int type, int start_r, int start_c) {
    int start_id = get_id(start_r, start_c);
    int* d = dist_matrix[type][start_id];
    fill(d, d + 90, -1);
    
    d[start_id] = 0;
    queue<pair<int, int>> q;
    q.push({start_r, start_c});
    
    while (!q.empty()) {
        auto [r, c] = q.front();
        q.pop();
        int curr_dist = d[get_id(r, c)];
        
        vector<pair<int, int>> next_moves;
        
        if (type == 0) { 
            int dr[] = {1, -1, 0, 0};
            int dc[] = {0, 0, 1, -1};
            for (int i = 0; i < 4; i++) next_moves.push_back({r + dr[i], c + dc[i]});
        }
        else if (type == 1) { 
            int dr[] = {1, 1, -1, -1};
            int dc[] = {1, -1, 1, -1};
            for (int i = 0; i < 4; i++) next_moves.push_back({r + dr[i], c + dc[i]});
        }
        else if (type == 2) { 
            
            
            for (int nr = 0; nr < ROWS; nr++) if (nr != r) next_moves.push_back({nr, c});
            for (int nc = 0; nc < COLS; nc++) if (nc != c) next_moves.push_back({r, nc});
        }
        else if (type == 3) { 
            int dr[] = {2, 2, -2, -2, 1, 1, -1, -1};
            int dc[] = {1, -1, 1, -1, 2, -2, 2, -2};
            for (int i = 0; i < 8; i++) next_moves.push_back({r + dr[i], c + dc[i]});
        }
        else if (type == 4) { 
            int dr[] = {2, 2, -2, -2};
            int dc[] = {2, -2, 2, -2};
            for (int i = 0; i < 4; i++) next_moves.push_back({r + dr[i], c + dc[i]});
        }
        else if (type == 5) { 
            if (r <= 4) {
                next_moves.push_back({r + 1, c});
            } else {
                next_moves.push_back({r + 1, c});
                next_moves.push_back({r, c + 1});
                next_moves.push_back({r, c - 1});
            }
        }
        
        for (auto p : next_moves) {
            if (valid(p.first, p.second)) {
                int nid = get_id(p.first, p.second);
                if (d[nid] == -1) {
                    d[nid] = curr_dist + 1;
                    q.push(p);
                }
            }
        }
    }
}

void precompute() {
    for (int t = 0; t < 6; t++) {
        for (int r = 0; r < ROWS; r++) {
            for (int c = 0; c < COLS; c++) {
                bfs(t, r, c);
            }
        }
    }
}

struct Candidate {
    int type; 
    int r, c;
};





int main(int argc, char* argv[]) {
    registerInteraction(argc, argv);
    atexit(log_metrics);
    
    
    precompute();
    
    
    int n = inf.readInt();
    vector<Pos> A(n);
    for (int i = 0; i < n; i++) {
        A[i].r = inf.readInt();
        A[i].c = inf.readInt();
    }
    
    
    
    
    cout << n << endl;
    for (int i = 0; i < n; i++) {
        cout << A[i].r << " " << A[i].c << endl;
    }
    cout.flush();
    
    
    vector<Candidate> candidates;
    for (int t = 0; t < 6; t++) {
        for (const auto& p : A) {
            candidates.push_back({t, p.r, p.c});
        }
    }
    
    
    long long m_contestant;
    if (!(cin >> m_contestant)) {
        finish(_wa, "Failed to read m");
    }
    query_limit = m_contestant;
    log_metrics(); 
    
    
    for (int k = 0; k < m_contestant; k++) {
        string token;
        if (!(cin >> token)) {
            finish(_wa, "Unexpected EOF expecting query");
        }
        
        if (token != "?") {
            finish(_wa, "Expected '?'");
        }
        
        int r, c;
        if (!(cin >> r >> c)) {
            finish(_wa, "Failed to read coordinates");
        }
        
        if (!valid(r, c)) {
            finish(_wa, "Invalid coordinates");
        }
        
        queries++; 
        
        
        map<int, vector<Candidate>> groups;
        int target_id = get_id(r, c);
        
        for (const auto& cand : candidates) {
            int start_id = get_id(cand.r, cand.c);
            int d = dist_matrix[cand.type][start_id][target_id];
            groups[d].push_back(cand);
        }
        
        
        
        int best_d = -2;
        size_t best_size = 0;
        
        for (auto const& [d, group] : groups) {
            if (group.size() > best_size) {
                best_size = group.size();
                best_d = d;
            }
        }
        
        if (best_d == -2) {
            
            finish(_fail, "Internal logic error: no candidates");
        }
        
        
        candidates = groups[best_d];
        
        
        cout << best_d << endl;
    }
    
    
    string token;
    if (!(cin >> token)) {
        finish(_wa, "Unexpected EOF expecting answer");
    }
    
    if (token != "!") {
        finish(_wa, "Expected '!'");
    }
    
    string type_str;
    if (!(cin >> type_str)) {
        finish(_wa, "Failed to read piece type");
    }
    
    if (type_str.size() != 1) {
        finish(_wa, "Invalid piece type format");
    }
    
    int guessed_idx = get_piece_idx(type_str[0]);
    if (guessed_idx == -1) {
        finish(_wa, "Unknown piece symbol");
    }
    
    
    bool possible = true;
    for (const auto& cand : candidates) {
        if (cand.type != guessed_idx) {
            possible = false;
            break;
        }
    }
    
    if (possible) {
        finish(_ok, "Correct");
    } else {
        finish(_wa, "Wrong guess (ambiguous)");
    }
    
    return 0;
}


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

using namespace std;


const string PIECES = "JSCMXB";

struct Point {
    int r, c;
};

int main(int argc, char* argv[]) {
    
    registerGen(argc, argv, 1);

    
    string mode = opt<string>("mode", "non");

    
    int seed = atoi(argv[1]);

    
    int R = 10;
    int C = 9;

    
    vector<Point> all_points;
    all_points.reserve(R * C);
    for (int r = 0; r < R; ++r) {
        for (int c = 0; c < C; ++c) {
            all_points.push_back({r, c});
        }
    }

    int n;
    vector<Point> A;

    
    if (seed == 1) {
        
        n = 1;
        A.push_back(all_points[rnd.next((int)all_points.size())]);
    } else if (seed == 2) {
        
        n = 90;
        A = all_points;
        shuffle(A.begin(), A.end());
    } else if (seed == 3) {
        
        n = 45;
        shuffle(all_points.begin(), all_points.end());
        for (int i = 0; i < n; ++i) {
            A.push_back(all_points[i]);
        }
    } else {
        
        int strategy = rnd.next(3);
        if (strategy == 0) {
            
            n = rnd.next(2, 10);
        } else if (strategy == 1) {
            
            n = rnd.next(75, 89);
        } else {
            
            n = rnd.next(1, 90);
        }
        
        
        shuffle(all_points.begin(), all_points.end());
        for (int i = 0; i < n; ++i) {
            A.push_back(all_points[i]);
        }
    }

    
    println(n);
    
    for (const auto& p : A) {
        println(p.r, p.c);
    }

    
    if (mode == "non") {
        
        int idx = rnd.next(n);
        Point secretPos = A[idx];
        char secretType = PIECES[rnd.next((int)PIECES.size())];
        
        
        println(secretType, secretPos.r, secretPos.c);
    }
    
    

    return 0;
}