Problem: icpc2023_nwerc_l
Difficulty: easy
Categories: data structures, math
Time limit: 2000 ms, memory limit: 256 MB

You are playing Battleships in a large ocean with large ships. More precisely, there is a large square grid of size at most $1 0 0 \times 1 0 0$ and inside it are up to 10 of the largest type of ship in Battleships – the aircraft carrier – which has a length of five tiles, placed either horizontally or vertically. The ships do not overlap, but they are allowed to be adjacent to each other. See Figure L.1 for an example.

Unfortunately, your opponent appears to bend the rules to their liking. It looks like they do not always determine the placement of their ships before you start shooting. You are not impressed by their attempt at cheating, and decide to try and win the game anyway.

![](images/508b533007e7a80874db8d7841af000e2cb15b725419376f34a0a2393b0e9927.jpg)

![](images/bf5ec4ed303829fb07ab28c50253fe1af2e96176b5528049201c3bae34f5682e.jpg)  
Figure L.1: Illustration of Sample Interaction 1 after the first four shots were fired.

Your goal is to locate and sink all your opponent’s aircraft carriers in at most 2500 shots, that is, you must hit each of the five tiles of all their ships.

# Interaction

This is an interactive problem. Your submission will be run against an interactor, which reads from the standard output of your submission and writes to the standard input of your submission. This interaction needs to follow a specific protocol:

The interactor first sends one line with two integers $n$ and $k$ $( 5 \leq n \leq 1 0 0 , 1 \leq k \leq 1 0 )$ ), the size of the grid and the number of ships. It is guaranteed that it is possible to place $k$ aircraft carriers in the grid without overlap.

Then, your program needs to start firing shots. Each shot is fired by printing one line of the form “ $\cdot _ { x }$ y” $( 1 \leq x , y \leq n )$ , indicating you shoot at position $( x , y )$ . The interactor will respond with “hit” if the shot was a hit, “sunk” if the shot caused an aircraft carrier to sink, and “miss” otherwise. If you have shot the same location before, the response will be “miss”.

Once you sink the last aircraft carrier, the interaction will stop and your program must exit.

The interactor is adaptive: the positions of the ships may be determined during the interaction, and may depend on where you decide to shoot.

Make sure you flush the buffer after each write.

A testing tool is provided to help you develop your solution.

Firing more than 2500 shots will result in a wrong answer.

<table><tr><td rowspan=2 colspan=2>Read                                   Sample Interaction 1                                  Write72</td></tr><tr><td rowspan=1 colspan=1>72</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>61</td></tr><tr><td rowspan=1 colspan=1>miss</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>63</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>73</td></tr><tr><td rowspan=1 colspan=1>miss</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>53</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>43</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>33</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>23</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>13</td></tr><tr><td rowspan=1 colspan=1>sunk</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>67</td></tr><tr><td rowspan=1 colspan=1>miss</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>67</td></tr><tr><td rowspan=1 colspan=1>miss</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>62</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>62</td></tr><tr><td rowspan=1 colspan=1>miss</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>64</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>65</td></tr><tr><td rowspan=1 colspan=1>hit</td><td rowspan=1 colspan=1></td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>66</td></tr><tr><td rowspan=1 colspan=1>sunk</td><td rowspan=1 colspan=1></td></tr></table>

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

using namespace std;


const int MAXN = 105;
int N, K;
int grid[MAXN][MAXN];     
int ship_health[15];      
bool shot[MAXN][MAXN];    
int sunk_count = 0;


long long queries = 0;
long long query_limit = 2500; 


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 parse_int(const string& s) {
    try {
        size_t pos;
        long long val = stoll(s, &pos);
        if (pos != s.size()) return -1; 
        if (val < -1000000 || val > 1000000) return -1; 
        return (int)val;
    } catch (...) {
        return -1;
    }
}

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

    
    
    
    
    
    N = inf.readInt(5, 100, "N");
    K = inf.readInt(1, 10, "K");
    
    memset(grid, 0, sizeof(grid));
    memset(shot, false, sizeof(shot));
    for (int i = 1; i <= K; i++) ship_health[i] = 5;

    for (int i = 1; i <= K; i++) {
        int r = inf.readInt(1, N, "r");
        int c = inf.readInt(1, N, "c");
        string dToken = inf.readToken(); 
        
        char dir = ' ';
        if (dToken == "h") dir = 'h';
        else if (dToken == "v") dir = 'v';
        else quitf(_fail, "Invalid direction token in input: %s", dToken.c_str());
        
        
        if (dir == 'h') {
            for (int k = 0; k < 5; k++) {
                if (c + k > N) quitf(_fail, "Ship out of bounds (input)");
                grid[r][c + k] = i;
            }
        } else {
            for (int k = 0; k < 5; k++) {
                if (r + k > N) quitf(_fail, "Ship out of bounds (input)");
                grid[r + k][c] = i;
            }
        }
    }

    
    cout << N << " " << K << endl;
    log_metrics(); 

    
    long long hard_cap = 30000; 

    while (true) {
        
        if (queries > 0 && queries % 10 == 0) log_metrics();
        
        if (queries > hard_cap) {
            finish(_pe, "Hard query limit exceeded");
        }

        string token;
        if (!(cin >> token)) {
            finish(_wa, "Unexpected EOF (ships remaining)");
        }

        int x = -1, y = -1;
        
        
        if (token == "?") {
            string sx, sy;
            if (!(cin >> sx >> sy)) finish(_wa, "Expected x y after ?");
            x = parse_int(sx);
            y = parse_int(sy);
        } else if (token == "!") {
             finish(_wa, "Protocol violation: unexpected '!'");
        } else {
            
            x = parse_int(token);
            string sy;
            if (!(cin >> sy)) finish(_wa, "Expected y");
            y = parse_int(sy);
        }

        
        if (x < 1 || x > N || y < 1 || y > N) {
            finish(_wa, "Coordinates out of bounds: " + to_string(x) + " " + to_string(y));
        }

        queries++;

        string response = "miss";
        
        if (shot[x][y]) {
            response = "miss"; 
        } else {
            shot[x][y] = true;
            if (grid[x][y] == 0) {
                response = "miss";
            } else {
                int id = grid[x][y];
                ship_health[id]--;
                if (ship_health[id] == 0) {
                    response = "sunk";
                    sunk_count++;
                } else {
                    response = "hit";
                }
            }
        }

        cout << response << endl;

        
        if (sunk_count == K) {
            finish(_ok, "All ships sunk");
        }
    }

    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 = 2500;
static const long long HARD_CAP = 25000;

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 N, K;
enum CellState { UNKNOWN = 0, MISS_LOG = 1, HIT_LOG = 2 };


vector<vector<int>> history_grid; 
vector<vector<int>> ship_occupancy; 

struct Ship {
    int id;
    int r, c; 
    int dir;  
    vector<pair<int,int>> hits; 
};
vector<Ship> ships;

struct Pos {
    int r, c, dir;
};
vector<Pos> all_positions;


mt19937 rng(589214);





bool is_valid_placement(int r, int c, int dir, int ignore_ship_id = -1) {
    if (dir == 0) {
        if (r < 1 || r > N) return false;
        if (c < 1 || c + 4 > N) return false;
    } else {
        if (r < 1 || r + 4 > N) return false;
        if (c < 1 || c > N) return false;
    }
    
    for (int i = 0; i < 5; ++i) {
        int nr = (dir == 0) ? r : r + i;
        int nc = (dir == 0) ? c + i : c;
        
        
        int occ = ship_occupancy[nr][nc];
        if (occ != -1 && occ != ignore_ship_id) return false;
        
        
        if (history_grid[nr][nc] == MISS_LOG) return false;
    }
    return true;
}


void place_ship_on_board(const Ship& s) {
    for (int i = 0; i < 5; ++i) {
        int nr = (s.dir == 0) ? s.r : s.r + i;
        int nc = (s.dir == 0) ? s.c + i : s.c;
        ship_occupancy[nr][nc] = s.id;
    }
}


void remove_ship_from_board(const Ship& s) {
    for (int i = 0; i < 5; ++i) {
        int nr = (s.dir == 0) ? s.r : s.r + i;
        int nc = (s.dir == 0) ? s.c + i : s.c;
        ship_occupancy[nr][nc] = -1;
    }
}


void init_all_positions() {
    for (int r = 1; r <= N; ++r) {
        for (int c = 1; c <= N; ++c) {
            if (c + 4 <= N) all_positions.push_back({r, c, 0});
            if (r + 4 <= N) all_positions.push_back({r, c, 1});
        }
    }
    shuffle(all_positions.begin(), all_positions.end(), rng);
}

int main(int argc, char** argv) {
    registerInteraction(argc, argv);
    atexit(log_metrics);
    
    
    
    N = inf.readInt(5, 100, "N");
    K = inf.readInt(1, 10, "K");
    
    
    cout << N << " " << K << endl;
    
    
    history_grid.assign(N + 2, vector<int>(N + 2, UNKNOWN));
    ship_occupancy.assign(N + 2, vector<int>(N + 2, -1));
    init_all_positions();
    
    
    ships.resize(K);
    for (int i = 0; i < K; ++i) {
        ships[i].id = i;
        bool placed = false;
        
        
        for (int att = 0; att < 2000; ++att) {
            int dir = rng() % 2;
            int r = (rng() % N) + 1;
            int c = (rng() % N) + 1;
            if (is_valid_placement(r, c, dir, i)) {
                ships[i].r = r; ships[i].c = c; ships[i].dir = dir;
                place_ship_on_board(ships[i]);
                placed = true;
                break;
            }
        }
        
        if (!placed) {
            for (const auto& p : all_positions) {
                if (is_valid_placement(p.r, p.c, p.dir, i)) {
                    ships[i].r = p.r; ships[i].c = p.c; ships[i].dir = p.dir;
                    place_ship_on_board(ships[i]);
                    placed = true;
                    break;
                }
            }
        }
        if (!placed) finish(_fail, "Cannot place ships initially");
    }
    
    log_metrics(); 
    
    int sunk_count = 0;
    
    while (true) {
        if (queries > HARD_CAP) finish(_pe, "Hard query limit exceeded");
        
        string token;
        if (!(cin >> token)) break; 
        
        int r, c;
        try {
            r = stoi(token);
            if (!(cin >> c)) break;
        } catch (...) {
            finish(_pe, "Expected integer coordinates");
            return 0;
        }
        
        queries++;
        if (r < 1 || r > N || c < 1 || c > N) finish(_pe, "Coordinates out of bounds");
        
        
        if (queries % 50 == 0) log_metrics();
        
        
        if (history_grid[r][c] != UNKNOWN) {
            cout << "miss" << endl;
            continue;
        }
        
        int sid = ship_occupancy[r][c];
        
        if (sid == -1) {
            
            history_grid[r][c] = MISS_LOG;
            cout << "miss" << endl;
        } else {
            
            
            
            
            Ship& s = ships[sid];
            remove_ship_from_board(s);
            
            bool dodged = false;
            
            if (s.hits.empty()) {
                
                for (const auto& p : all_positions) {
                    
                    bool covers_target = false;
                    if (p.dir == 0) {
                        if (p.r == r && c >= p.c && c < p.c + 5) covers_target = true;
                    } else {
                        if (p.c == c && r >= p.r && r < p.r + 5) covers_target = true;
                    }
                    if (covers_target) continue;
                    
                    if (is_valid_placement(p.r, p.c, p.dir, s.id)) {
                        s.r = p.r; s.c = p.c; s.dir = p.dir;
                        place_ship_on_board(s);
                        dodged = true;
                        break;
                    }
                }
            } else {
                
                
                int hr = s.hits[0].first;
                int hc = s.hits[0].second;
                
                
                vector<Pos> candidates;
                for (int start_c = hc - 4; start_c <= hc; ++start_c) candidates.push_back({hr, start_c, 0});
                for (int start_r = hr - 4; start_r <= hr; ++start_r) candidates.push_back({start_r, hc, 1});
                
                shuffle(candidates.begin(), candidates.end(), rng);
                
                for (const auto& p : candidates) {
                    
                    bool covers_target = false;
                    if (p.dir == 0) {
                        if (p.r == r && c >= p.c && c < p.c + 5) covers_target = true;
                    } else {
                        if (p.c == c && r >= p.r && r < p.r + 5) covers_target = true;
                    }
                    if (covers_target) continue;

                    
                    if (!is_valid_placement(p.r, p.c, p.dir, s.id)) continue;
                    
                    
                    bool all_hits = true;
                    for (auto& h : s.hits) {
                        bool hit_ok = false;
                        if (p.dir == 0) {
                            if (h.first == p.r && h.second >= p.c && h.second < p.c + 5) hit_ok = true;
                        } else {
                            if (h.second == p.c && h.first >= p.r && h.first < p.r + 5) hit_ok = true;
                        }
                        if (!hit_ok) { all_hits = false; break; }
                    }
                    
                    if (all_hits) {
                        s.r = p.r; s.c = p.c; s.dir = p.dir;
                        place_ship_on_board(s);
                        dodged = true;
                        break;
                    }
                }
            }
            
            if (dodged) {
                history_grid[r][c] = MISS_LOG;
                cout << "miss" << endl;
            } else {
                
                place_ship_on_board(s); 
                history_grid[r][c] = HIT_LOG;
                s.hits.push_back({r, c});
                
                if (s.hits.size() == 5) {
                    sunk_count++;
                    cout << "sunk" << endl;
                    if (sunk_count == K) finish(_ok, "All ships sunk");
                } else {
                    cout << "hit" << endl;
                }
            }
        }
    }
    
    
    finish(_wa, "Solution exited before sinking all ships");
}


Test generator (C++):











#include "testlib.h"
#include <bits/stdc++.h>

using namespace std;

struct Ship {
    int r, c;
    char dir; 
};


bool can_place(int N, int r, int c, char dir, const vector<vector<bool>>& grid) {
    if (dir == 'h') {
        if (c + 4 > N) return false;
        for (int k = 0; k < 5; k++) {
            if (grid[r][c + k]) return false;
        }
    } else {
        if (r + 4 > N) return false;
        for (int k = 0; k < 5; k++) {
            if (grid[r + k][c]) return false;
        }
    }
    return true;
}


void place_ship(int r, int c, char dir, vector<vector<bool>>& grid) {
    if (dir == 'h') {
        for (int k = 0; k < 5; k++) grid[r][c + k] = true;
    } else {
        for (int k = 0; k < 5; k++) grid[r + k][c] = true;
    }
}

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

    
    string mode = opt<string>("mode", "non");
    
    
    int seed_val = atoi(argv[1]);

    int N, K;

    
    if (seed_val == 1) {
        
        N = 5; K = 1;
    } else if (seed_val == 2) {
        
        N = 100; K = 10;
    } else if (seed_val == 3) {
        
        N = 10; K = 10;
    } else if (seed_val == 4) {
        
        N = 5; K = 5;
    } else {
        
        N = rnd.next(5, 100);
        
        
        
        
        
        int limit_k = min(10, N);
        
        int p = rnd.next(1, 100);
        if (p <= 10) K = 1; 
        else if (p <= 40) K = rnd.next(1, limit_k); 
        else K = limit_k; 
    }

    if (mode == "adp") {
        
        
        cout << N << " " << K << endl;
    } else {
        
        vector<Ship> ships;
        vector<vector<bool>> grid(N + 1, vector<bool>(N + 1, false));
        
        bool success = false;
        
        
        
        for (int attempt = 0; attempt < 50; attempt++) {
            ships.clear();
            for (int i = 1; i <= N; i++) fill(grid[i].begin(), grid[i].end(), false);
            
            int placed_count = 0;
            for (int i = 0; i < K; i++) {
                bool placed = false;
                
                for (int t = 0; t < 2000; t++) {
                    char dir = (rnd.next(2) ? 'h' : 'v');
                    int r, c;
                    if (dir == 'h') {
                        r = rnd.next(1, N);
                        c = rnd.next(1, N - 4);
                    } else {
                        r = rnd.next(1, N - 4);
                        c = rnd.next(1, N);
                    }
                    
                    if (can_place(N, r, c, dir, grid)) {
                        place_ship(r, c, dir, grid);
                        ships.push_back({r, c, dir});
                        placed = true;
                        placed_count++;
                        break;
                    }
                }
                if (!placed) break; 
            }
            if (placed_count == K) {
                success = true;
                break;
            }
        }
        
        
        
        if (!success) {
            ships.clear();
            for (int i = 1; i <= N; i++) fill(grid[i].begin(), grid[i].end(), false);
            int placed_count = 0;
            
            
            vector<int> rows(N);
            iota(rows.begin(), rows.end(), 1);
            shuffle(rows.begin(), rows.end());
            
            for (int r : rows) {
                if (placed_count >= K) break;
                
                int c = rnd.next(1, N - 4);
                if (can_place(N, r, c, 'h', grid)) {
                    place_ship(r, c, 'h', grid);
                    ships.push_back({r, c, 'h'});
                    placed_count++;
                }
            }
            
            K = placed_count;
        }
        
        
        for (int i = 0; i < (int)ships.size(); i++) {
            int j = rnd.next(i, (int)ships.size() - 1);
            swap(ships[i], ships[j]);
        }
        
        
        cout << N << " " << K << endl;
        for (const auto& s : ships) {
            cout << s.r << " " << s.c << " " << s.dir << endl;
        }
    }

    return 0;
}