Problem: icpc2023_nordic_collegiate_programming_b
Difficulty: medium
Categories: game, data structures, search
Time limit: 2000 ms, memory limit: 256 MB

Erik and his grandpa often go to the forest to pick blueberries. Grandpa always finds the most berries, and even though it is not a competition, Erik has had enough of this. He has observed that grandpa uses a simple greedy strategy to pick as many berries as possible. With this information, Erik will try to finally pick at least as many berries as grandpa.

![](images/45f070646c0f1e55c0c88f578193eb379418410163659f5e4986c1ac6796b012.jpg)  
Picture by MAKY.OREL, public domain

The blueberry shrubs can be represented as a string $s =$ $s _ { 1 } s _ { 2 } \ldots s _ { N }$ of length $N = 1 0 ^ { 5 }$ , consisting of characters . and b. If $s _ { i } = \mathrm { b }$ , then there is a berry at position $i$ , otherwise there is no berry there. There will be exactly $N / 2$ berries to start with, and they are distributed uniformly at random.

The berry picking takes place in turns. In one move, a person can choose a position $i$ $( 1 \leq i \leq N - 3 )$ , and pick all the berries at positions $i , i + 1 , i + 2$ , and $i + 3$ . Grandpa makes the first move, then Erik makes a move, then grandpa, and so on. Grandpa will always greedily make a move that picks as many berries as possible. Among all such moves, he will pick one uniformly at random.

You are given the string $s$ , and your task is to write a program that decides what moves Erik should make in order to pick at least as many berries as grandpa.

# Interaction

The first line of input contains the number $N$ , the length of the string $s$ . This number will always be equal to $1 0 ^ { 5 }$ , except for in the sample. Your program does not have to solve the sample to get accepted.

The second line contains the string $s$ of length $N$ . This string has exactly $N / 2$ characters b, and their positions are generated uniformly at random.

Then, your program should start interacting with the grader. When it is grandpa’s turn, you should read a single integer $i$ $1 \leq i \leq N - 3 )$ , meaning that grandpa picks the berries at positions $i , i + 1 , i + 2$ , and $i + 3$ . When it is your turn, you should instead print a number $i$ meaning that you pick the berries at positions $i , i + 1 , i + 2$ , and $i + 3$ .

If it is your turn and there are no more berries, your program should terminate without printing anything more. If it is grandpa’s turn and there are no berries, then the grader will not make any more moves, and your program should also terminate without printing anything more. If you picked at least as many berries as grandpa, you pass the test case.

Each turn, grandpa greedily makes a move that picks as many berries as possible, and among all such moves, he chooses one uniformly at random.

The strings $s$ were generated beforehand, so they are the same across different submissions. However, grandpa’s behaviour is randomized every submission, so it is possible to get different results if you submit the same code twice.

There will be 100 test cases. It is guaranteed that there exists a solution that passes this many cases with very high probability.

# Example

In the sample, Erik and grandpa get 5 berries each, which means that Erik achieved his goal. At the very start of the game, the maximum number of berries possible to pick in one move is 3. There are a 4 possible choices of $i$ that gets 3 berries: 1, 2, 3 and 6. Grandpa chooses one of these uniformly at random which ends up being $i = 2$ . After that, Erik chooses $i = 6$ which gives him three berries as well. In the last two turns, both grandpa and Erik pick two berries.

<table><tr><td colspan="3">Read Sample Interaction 1</td></tr><tr><td colspan="2">20 .bbb.b.bb...b.b...bb</td><td></td></tr><tr><td colspan="2">2</td><td></td></tr><tr><td colspan="2">6</td><td></td></tr><tr><td>13</td><td>17</td><td></td></tr></table>

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 int N;
static string s;
static int berries_remaining = 0;
static long long erik_score = 0;
static long long grandpa_score = 0;




static vector<int> val;             
static vector<int> pos_in_list;     
static vector<int> lists[5];        




static unsigned long long seed = 84421337ULL;
unsigned long long next_rand() {
    seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
    return seed;
}


int get_rand_index(int range) {
    if (range <= 0) return 0;
    return (int)(next_rand() % range);
}




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

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




void add_to_list(int w, int v) {
    pos_in_list[w] = (int)lists[v].size();
    lists[v].push_back(w);
}

void remove_from_list(int w, int v) {
    int idx = pos_in_list[w];
    int last_w = lists[v].back();
    
    
    lists[v][idx] = last_w;
    pos_in_list[last_w] = idx;
    lists[v].pop_back();
}

int count_window(int w) {
    int c = 0;
    for (int k = 0; k < 4; k++) {
        if (s[w + k] == 'b') c++;
    }
    return c;
}

void update_window(int w) {
    int old_v = val[w];
    remove_from_list(w, old_v);
    
    int new_v = count_window(w);
    val[w] = new_v;
    add_to_list(w, new_v);
}


int apply_move(int m) {
    
    int collected = 0;
    vector<int> affected_windows;
    
    
    for (int k = 0; k < 4; k++) {
        int idx = m + k;
        if (s[idx] == 'b') {
            collected++;
            s[idx] = '.';
            
            
            int start_w = max(0, idx - 3);
            int end_w = min(N - 4, idx);
            for (int w = start_w; w <= end_w; w++) {
                affected_windows.push_back(w);
            }
        }
    }
    
    
    if (!affected_windows.empty()) {
        sort(affected_windows.begin(), affected_windows.end());
        affected_windows.erase(unique(affected_windows.begin(), affected_windows.end()), affected_windows.end());
        for (int w : affected_windows) {
            update_window(w);
        }
    }
    
    berries_remaining -= collected;
    return collected;
}




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

    
    N = inf.readInt();
    s = inf.readToken();
    
    if ((int)s.size() != N) {
        finish(_fail, "Input string length does not match N");
    }

    berries_remaining = 0;
    for (char c : s) if (c == 'b') berries_remaining++;
    
    
    query_limit = N; 
    long long hard_cap = max(200000LL, 10LL * N);

    
    if (N >= 4) {
        val.resize(N - 3);
        pos_in_list.resize(N - 3);
        for (int i = 0; i <= N - 4; i++) {
            val[i] = count_window(i);
            add_to_list(i, val[i]);
        }
    }

    
    cout << N << endl;
    cout << s << endl;
    cout.flush();

    
    
    int turn = 0; 
    
    while (berries_remaining > 0) {
        if (queries > hard_cap) {
            finish(_pe, "Hard query limit exceeded (infinite loop?)");
        }

        if (turn == 0) { 
            int best_v = -1;
            
            for (int v = 4; v >= 0; v--) {
                if (!lists[v].empty()) {
                    best_v = v;
                    break;
                }
            }
            
            
            if (best_v == -1) break; 
            
            
            int count = (int)lists[best_v].size();
            int r = get_rand_index(count);
            int move_idx = lists[best_v][r];
            
            int pts = apply_move(move_idx);
            grandpa_score += pts;
            
            cout << (move_idx + 1) << endl; 
            
            
            turn = 1;
        } else { 
            
            
            char c;
            while (cin.get(c) && isspace(c)); 
            if (cin.eof()) {
                finish(_wa, "Unexpected EOF waiting for Erik's move");
            }
            cin.putback(c);
            
            string token;
            if (!(cin >> token)) {
                finish(_wa, "Failed to read token");
            }
            
            if (token == "?") {
                if (!(cin >> token)) finish(_wa, "Expected number after '?'");
            } else if (token == "!") {
                finish(_pe, "Unexpected protocol token '!'");
            }
            
            int move_1 = 0;
            try {
                move_1 = stoi(token);
            } catch (...) {
                finish(_pe, "Move '" + token + "' is not a valid integer");
            }
            
            if (move_1 < 1 || move_1 > N - 3) {
                finish(_wa, "Move " + to_string(move_1) + " out of bounds [1, " + to_string(N-3) + "]");
            }
            
            queries++; 
            
            int pts = apply_move(move_1 - 1); 
            erik_score += pts;
            
            turn = 0;
        }
    }

    
    if (erik_score >= grandpa_score) {
        finish(_ok, "Erik " + to_string(erik_score) + " >= Grandpa " + to_string(grandpa_score));
    } else {
        finish(_wa, "Erik " + to_string(erik_score) + " < Grandpa " + to_string(grandpa_score));
    }

    return 0;
}


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", 100000);

    
    if (n < 4) n = 4;        
    if (n % 2 != 0) n++;     

    int berries = n / 2;
    string s(n, '.');

    
    if (mode == "non") {
        
        
        for (int i = 0; i < berries; i++) {
            s[i] = 'b';
        }
        shuffle(s.begin(), s.end());
    } else {
        
        
        int type = rnd.next(1, 5);

        if (type == 1) {
            
            for (int i = 0; i < berries; i++) s[i] = 'b';
            
            int swaps = n / 50; 
            for (int i = 0; i < swaps; i++) {
                int u = rnd.next(n);
                int v = rnd.next(n);
                swap(s[u], s[v]);
            }
        } else if (type == 2) {
            
            for (int i = 0; i < n; i++) {
                if (i % 2 == 0) s[i] = 'b';
            }
            
            int swaps = n / 100;
            for (int i = 0; i < swaps; i++) {
                int u = rnd.next(n);
                int v = rnd.next(n);
                swap(s[u], s[v]);
            }
        } else if (type == 3) {
            
            
            for (int i = 0; i < n; i++) {
                if ((i % 8) < 4) s[i] = 'b';
            }
            
            int current_b = 0;
            for (char c : s) if (c == 'b') current_b++;
            
            vector<int> b_idx, dot_idx;
            for (int i = 0; i < n; i++) {
                if (s[i] == 'b') b_idx.push_back(i);
                else dot_idx.push_back(i);
            }
            shuffle(b_idx.begin(), b_idx.end());
            shuffle(dot_idx.begin(), dot_idx.end());
            
            while (current_b > berries) {
                s[b_idx.back()] = '.';
                b_idx.pop_back();
                current_b--;
            }
            while (current_b < berries) {
                s[dot_idx.back()] = 'b';
                dot_idx.pop_back();
                current_b++;
            }
        } else if (type == 4) {
            
            int current_b = 0;
            int ptr = 0;
            while (ptr < n) {
                int len = rnd.next(10, 200);
                if (ptr + len > n) len = n - ptr;
                
                
                bool dense = rnd.next(2);
                if (dense) {
                    for (int k = 0; k < len && current_b < berries; k++) {
                        s[ptr + k] = 'b';
                        current_b++;
                    }
                }
                ptr += len;
            }
            
            vector<int> dots;
            for (int i = 0; i < n; i++) if (s[i] == '.') dots.push_back(i);
            shuffle(dots.begin(), dots.end());
            int needed = berries - current_b;
            for (int i = 0; i < needed; i++) s[dots[i]] = 'b';
        } else {
            
            for (int i = n - berries; i < n; i++) s[i] = 'b';
            
            int swaps = n / 50;
            for (int i = 0; i < swaps; i++) {
                int u = rnd.next(n);
                int v = rnd.next(n);
                swap(s[u], s[v]);
            }
        }
    }

    
    cout << n << endl;
    cout << s << endl;

    return 0;
}