Problem: icpc2022_swerc_e
Difficulty: easy
Categories: game, greedy, math
Time limit: 2000 ms, memory limit: 256 MB

The two siblings Alberto and Beatrice have to eat a spinach pizza together. However, none of them likes spinach, so they both want to eat as little as possible.

The pizza has the shape of a strictly convex polygon with $n$ vertices located at integer coordinates $( x _ { 1 } , y _ { 1 } )$ , $( x _ { 2 } , y _ { 2 } )$ , . . . , $( x _ { n } , y _ { n } )$ of the plane.

The siblings have decided to eat the pizza in the following way: taking turns, starting with Alberto, each sibling chooses a vertex of the remaining part of the pizza and eats out the triangle determined by its two neighboring edges. In this way, after each of the first $n - 3$ turns the pizza will have one less vertex. The game ends after the $( n - 2 )$ -th turn, when all the pizza has been eaten.

Assuming that Alberto and Beatrice choose the slices to eat optimally, which of the siblings manages to eat at most half of the pizza? You should identify a sibling that has a strategy to do so and help them choose the slices appropriately. Note that it is possible that both Alberto and Beatrice end up eating exactly half of the area if they choose their slices optimally.

# Input

The first line contains a single integer $n$ ( $4 \leq n \leq 1 0 0$ ) — the number of vertices.

The next $n$ lines contain two integers $x _ { i }$ and $y _ { i }$ each $( - 1 0 ^ { 6 } \leq x _ { i } , y _ { i } \leq 1 0 ^ { 6 } )$ — the coordinates of the $i$ -th vertex of the polygon representing the initial shape of the pizza.

It is guaranteed that the polygon is strictly convex and that its vertices are given in counterclockwise order.

# Interaction

First, you should print a line containing either the string Alberto or the string Beatrice — the sibling that you will help to win.

Then, for the next $n - 2$ turns, you will alternate with the judge in choosing a slice of the pizza and removing it, starting with you if you chose to help Alberto, or starting with the judge if you chose to help Beatrice.

• When it is your turn, print a single line containing an integer $p$ $1 \leq p \leq n$ ) that has not been chosen before, indicating that you want to eat the slice determined by the vertex located at $( x _ { p } , y _ { p } )$ .   
• When it is the judge’s turn, read an integer $q$ ( $1 \leq q \leq n$ ), indicating that the other player eats the slice determined by the vertex located at $( x _ { q } , y _ { q } )$ . It is guaranteed that $q$ has not been chosen before.

If one of your interactions is malformed, the interactor terminates immediately and your program receives the verdict WRONG-ANSWER. Otherwise, you will receive CORRECT if at the end your player has eaten at most half of the pizza, and WRONG-ANSWER otherwise.

After printing a line do not forget to end the line and flush the output. Otherwise, you will get the verdict TIMELIMIT. To flush the output, use:

• fflush(stdout) in C;   
• fflush(stdout), cout << flush or cout.flush() in $\mathrm { C } + +$ ;   
• System.out.flush() in Java and Kotlin;   
• sys.stdout.flush() in Python.

# Samples

<table><tr><td>Sample input 1</td><td>Sample output 1</td></tr><tr><td>4</td><td></td></tr><tr><td>00</td><td></td></tr><tr><td>61</td><td></td></tr><tr><td>53</td><td></td></tr><tr><td>14</td><td></td></tr></table>

# Explanation of sample 1.

The pizza has area 15. Alberto can eat less than half of the pizza by eating the slice around vertex 2 (which has area 6.5) or around vertex 3 (which has area 3.5).

![](images/339e2e47498a78eb31ae2887979e3d7034279ddd0e1afb2bdd773ac5564dbeb1.jpg)

<table><tr><td>Sample input 2</td><td>Sample output 2</td></tr><tr><td>6</td><td></td></tr><tr><td>00</td><td></td></tr><tr><td>20</td><td></td></tr><tr><td>32</td><td></td></tr><tr><td>24</td><td></td></tr><tr><td>04</td><td></td></tr><tr><td>-12</td><td></td></tr></table>

# Explanation of sample 2.

It can be proved that both players will eat exactly half of the pizza if they eat optimally. Therefore it is possible to choose to help either Alberto or Beatrice.

<table><tr><td>Sample input 3</td><td>Sample output 3</td></tr><tr><td>7</td><td></td></tr><tr><td>00</td><td></td></tr><tr><td>20</td><td></td></tr><tr><td>52</td><td></td></tr><tr><td>45</td><td></td></tr><tr><td>15</td><td></td></tr><tr><td>-14</td><td></td></tr><tr><td>-12</td><td></td></tr></table>

# Explanation of sample 3.

It is possible to show that only Beatrice has a strategy to eat at most half of the pizza. The following is an example of a valid interaction (after reading the input):

<table><tr><td rowspan=1 colspan=1>ContestantJudge</td><td rowspan=1 colspan=1> Judge</td><td rowspan=1 colspan=1>Explanation</td></tr><tr><td rowspan=1 colspan=1>Beatrice</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>The contestant will help Beatrice</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>7</td><td rowspan=1 colspan=1>Alberto eats the triangle with vertices 6, 7, 1 and area 1</td></tr><tr><td rowspan=1 colspan=1>2</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>Beatrice eats the triangle with vertices 1, 2, 3 and area 2</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>5</td><td rowspan=1 colspan=1>Alberto eats the triangle with vertices 4, 5, 6 and area 1.5</td></tr><tr><td rowspan=1 colspan=1>4</td><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>Beatrice eats the triangle with vertices 3, 4, 6 and area 8</td></tr><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>6</td><td rowspan=1 colspan=1>Alberto eats the triangle with vertices 3, 6, 1 and area 11</td></tr></table>

The total area eaten by Alberto is 13.5 and the total area eaten by Beatrice is 10, which is less than half the area of the whole pizza. The actions performed by the contestant and the judge in this example of interaction may be non-optimal. The process is illustrated below:

![](images/48c7d5575029cd11b2e47ff939f7e21ac329dc91714ed6966caba5ac10b27385.jpg)

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

using namespace std;


static int queries_count = 0;
static int query_limit = 0;

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

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


struct Point {
    long long x, y;
};



long long cross_product(Point a, Point b, Point c) {
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}


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

    
    
    int n = inf.readInt(4, 100, "n");
    vector<Point> poly(n);
    for (int i = 0; i < n; i++) {
        poly[i].x = inf.readLong(-1000000LL, 1000000LL, "x");
        poly[i].y = inf.readLong(-1000000LL, 1000000LL, "y");
    }
    
    
    query_limit = n - 2;

    
    
    cout << n << endl;
    for (int i = 0; i < n; i++) {
        cout << poly[i].x << " " << poly[i].y << endl;
    }
    cout.flush(); 

    
    
    string token;
    if (!(cin >> token)) {
        finish(_pe, "Unexpected end of input while reading player choice");
    }
    
    
    string choice = token;
    transform(choice.begin(), choice.end(), choice.begin(), ::tolower);
    
    bool contestant_is_alberto = false;
    if (choice == "alberto") {
        contestant_is_alberto = true;
    } else if (choice == "beatrice") {
        contestant_is_alberto = false;
    } else {
        finish(_wa, "Invalid player name: " + token);
    }

    
    long long total_area_2 = 0;
    
    for (int i = 1; i < n - 1; i++) {
        total_area_2 += cross_product(poly[0], poly[i], poly[i + 1]);
    }
    total_area_2 = abs(total_area_2);

    
    
    vector<int> current_indices(n);
    iota(current_indices.begin(), current_indices.end(), 0); 

    long long alberto_area_2 = 0;
    long long beatrice_area_2 = 0;

    
    for (int turn = 0; turn < n - 2; turn++) {
        
        if (turn > 0 && turn % 10 == 0) log_metrics();

        bool is_alberto_turn = (turn % 2 == 0);
        bool is_contestant_turn = (is_alberto_turn == contestant_is_alberto);

        int chosen_idx_in_current = -1;

        if (is_contestant_turn) {
            
            string move_str;
            if (!(cin >> move_str)) {
                finish(_pe, "Unexpected end of input waiting for move");
            }
            
            
            if (move_str == "?") {
                if (!(cin >> move_str)) finish(_pe, "Expected index after '?'");
            }
            
            
            if (!move_str.empty() && move_str[0] == '!') {
                finish(_wa, "Unexpected '!' token during game phase");
            }

            
            int p = -1;
            try {
                p = stoi(move_str);
            } catch (...) {
                finish(_pe, "Expected integer index, got: " + move_str);
            }

            
            int p0 = p - 1;

            
            bool found = false;
            for (int i = 0; i < (int)current_indices.size(); i++) {
                if (current_indices[i] == p0) {
                    chosen_idx_in_current = i;
                    found = true;
                    break;
                }
            }

            if (!found) {
                if (p0 < 0 || p0 >= n) finish(_wa, "Index out of bounds: " + to_string(p));
                else finish(_wa, "Vertex already removed: " + to_string(p));
            }

        } else {
            
            
            
            
            long long best_val = -1;
            int best_i = -1;
            
            int sz = current_indices.size();
            for (int i = 0; i < sz; i++) {
                int idx = current_indices[i];
                int prev = current_indices[(i - 1 + sz) % sz];
                int next = current_indices[(i + 1) % sz];
                
                long long area = abs(cross_product(poly[prev], poly[idx], poly[next]));
                
                
                if (best_i == -1 || area < best_val) {
                    best_val = area;
                    best_i = i;
                } else if (area == best_val) {
                    
                    if (current_indices[i] < current_indices[best_i]) {
                        best_i = i;
                    }
                }
            }
            
            chosen_idx_in_current = best_i;
            
            
            cout << (current_indices[chosen_idx_in_current] + 1) << endl;
        }

        
        int sz = current_indices.size();
        int idx = current_indices[chosen_idx_in_current];
        int prev = current_indices[(chosen_idx_in_current - 1 + sz) % sz];
        int next = current_indices[(chosen_idx_in_current + 1) % sz];
        
        long long area_eaten = abs(cross_product(poly[prev], poly[idx], poly[next]));
        
        if (is_alberto_turn) alberto_area_2 += area_eaten;
        else beatrice_area_2 += area_eaten;

        
        current_indices.erase(current_indices.begin() + chosen_idx_in_current);
        
        queries_count++;
    }

    
    
    long long contestant_area = contestant_is_alberto ? alberto_area_2 : beatrice_area_2;
    
    
    
    
    
    
    
    
    if (contestant_area <= total_area_2 / 2) {
        finish(_ok, "Contestant won. Area: " + to_string(contestant_area) + " (Limit: " + to_string(total_area_2 / 2) + ")");
    } else {
        finish(_wa, "Contestant lost. Area: " + to_string(contestant_area) + " (Limit: " + to_string(total_area_2 / 2) + ")");
    }

    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;


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


struct Point {
    long long x, y;
};


long long cross_product(const Point& a, const Point& b, const Point& c) {
    return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

long long get_area2(const Point& a, const Point& b, const Point& c) {
    return abs(cross_product(a, b, c));
}


int n;
vector<Point> poly;
vector<int> current_indices; 
long long total_area2 = 0;


bool contestant_is_alberto = false;
long long contestant_area2 = 0;
long long judge_area2 = 0;




unordered_map<unsigned long long, long long> memo;
vector<unsigned long long> zobrist;

void init_zobrist(int size) {
    mt19937_64 rng(1337);
    zobrist.resize(size + 1);
    for (int i = 1; i <= size; i++) {
        zobrist[i] = rng();
    }
}

unsigned long long compute_hash(const vector<int>& indices) {
    unsigned long long h = 0;
    for (int idx : indices) h ^= zobrist[idx];
    return h;
}






long long solve_state(const vector<int>& current, int depth, long long alpha, long long beta, long long current_diff, bool is_A_turn) {
    
    if (current.size() == 2) {
        return current_diff;
    }

    
    if (depth <= 0) {
        
        
        
        return current_diff;
    }

    
    unsigned long long h = 0;
    bool use_memo = (current.size() <= 20);
    if (use_memo) {
        h = compute_hash(current);
        
        
        
        
        if (memo.count(h)) {
            return current_diff + memo[h];
        }
    }

    int sz = current.size();
    long long best_val;

    if (is_A_turn) {
        
        
        best_val = 2e18; 

        for (int i = 0; i < sz; i++) {
            
            int idx = current[i]; 
            int prev_idx = current[(i - 1 + sz) % sz];
            int next_idx = current[(i + 1) % sz];

            long long tri_area = get_area2(poly[prev_idx-1], poly[idx-1], poly[next_idx-1]);

            
            vector<int> next_state;
            next_state.reserve(sz - 1);
            for(int k = 0; k < sz; k++) {
                if(k != i) next_state.push_back(current[k]);
            }

            
            long long val = solve_state(next_state, depth - 1, alpha, beta, current_diff + tri_area, !is_A_turn);

            if (val < best_val) best_val = val;
            
            
            
            beta = min(beta, best_val);
            if (best_val <= alpha) break; 
        }
    } else {
        
        
        best_val = -2e18; 

        for (int i = 0; i < sz; i++) {
            int idx = current[i];
            int prev_idx = current[(i - 1 + sz) % sz];
            int next_idx = current[(i + 1) % sz];

            long long tri_area = get_area2(poly[prev_idx-1], poly[idx-1], poly[next_idx-1]);

            vector<int> next_state;
            next_state.reserve(sz - 1);
            for(int k = 0; k < sz; k++) {
                if(k != i) next_state.push_back(current[k]);
            }

            
            long long val = solve_state(next_state, depth - 1, alpha, beta, current_diff - tri_area, !is_A_turn);

            if (val > best_val) best_val = val;

            
            alpha = max(alpha, best_val);
            if (best_val >= beta) break;
        }
    }

    if (use_memo) {
        memo[h] = best_val - current_diff;
    }
    return best_val;
}


int get_adaptive_depth(int n_left) {
    if (n_left <= 18) return 20; 
    if (n_left <= 25) return 6;
    if (n_left <= 40) return 4;
    return 3;
}

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

    
    n = inf.readInt();
    poly.resize(n);
    
    
    for (int i = 0; i < n; i++) {
        poly[i].x = inf.readLong();
        poly[i].y = inf.readLong();
        current_indices.push_back(i + 1); 
    }

    
    long long temp_area = 0;
    for (int i = 1; i < n - 1; i++) {
        temp_area += get_area2(poly[0], poly[i], poly[i+1]);
    }
    total_area2 = temp_area;
    
    query_limit = n - 2;
    log_metrics(); 

    
    cout << n << endl;
    for (const auto& p : poly) {
        cout << p.x << " " << p.y << endl;
    }
    cout << flush;

    
    init_zobrist(n);

    
    string choice = ouf.readToken();
    if (choice == "Alberto") {
        contestant_is_alberto = true;
    } else if (choice == "Beatrice") {
        contestant_is_alberto = false;
    } else {
        finish(_wa, "Expected 'Alberto' or 'Beatrice'");
    }

    
    
    
    
    
    
    bool judge_turn = !contestant_is_alberto;

    
    
    
    
    
    

    while (current_indices.size() > 2) {
        
        if (queries >= 200) {
            finish(_pe, "Too many moves (hard cap)");
        }
        
        
        
        
        
        bool is_A_turn = (queries % 2 == 0);

        if (judge_turn) {
            
            
            int depth = get_adaptive_depth(current_indices.size());
            long long best_val = is_A_turn ? 2e18 : -2e18;
            int best_move_idx = -1; 

            long long alpha = -2e18;
            long long beta = 2e18;
            
            
            int sz = current_indices.size();
            long long current_diff = contestant_is_alberto 
                                     ? (contestant_area2 - judge_area2) 
                                     : (judge_area2 - contestant_area2); 
            
            
            long long diff = (contestant_is_alberto) ? current_diff : -current_diff;

            
            
            for (int i = 0; i < sz; i++) {
                int idx = current_indices[i];
                int prev_idx = current_indices[(i - 1 + sz) % sz];
                int next_idx = current_indices[(i + 1) % sz];

                long long area = get_area2(poly[prev_idx-1], poly[idx-1], poly[next_idx-1]);
                
                vector<int> next_state;
                next_state.reserve(sz - 1);
                for (int k = 0; k < sz; k++) if(k != i) next_state.push_back(current_indices[k]);

                
                
                long long next_diff = is_A_turn ? (diff + area) : (diff - area);

                long long val = solve_state(next_state, depth - 1, alpha, beta, next_diff, !is_A_turn);

                if (is_A_turn) {
                    
                    if (val < best_val) {
                        best_val = val;
                        best_move_idx = i;
                    }
                    beta = min(beta, best_val); 
                } else {
                    
                    if (val > best_val) {
                        best_val = val;
                        best_move_idx = i;
                    }
                    alpha = max(alpha, best_val);
                }
            }

            
            int chosen_vertex = current_indices[best_move_idx];
            int prev_idx = current_indices[(best_move_idx - 1 + sz) % sz];
            int next_idx = current_indices[(best_move_idx + 1) % sz];
            long long eaten = get_area2(poly[prev_idx-1], poly[chosen_vertex-1], poly[next_idx-1]);
            
            judge_area2 += eaten;
            current_indices.erase(current_indices.begin() + best_move_idx);
            
            
            cout << chosen_vertex << endl;
            cout << flush;
            
        } else {
            
            int p = ouf.readInt();
            
            
            int move_idx = -1;
            for (int i = 0; i < (int)current_indices.size(); i++) {
                if (current_indices[i] == p) {
                    move_idx = i;
                    break;
                }
            }
            
            if (move_idx == -1) {
                finish(_wa, "Contestant chose an invalid or already eaten vertex " + to_string(p));
            }
            
            
            int sz = current_indices.size();
            int prev_idx = current_indices[(move_idx - 1 + sz) % sz];
            int next_idx = current_indices[(move_idx + 1) % sz];
            long long eaten = get_area2(poly[prev_idx-1], poly[p-1], poly[next_idx-1]);
            
            contestant_area2 += eaten;
            current_indices.erase(current_indices.begin() + move_idx);
        }

        
        judge_turn = !judge_turn;
        queries++;
        log_metrics(); 
    }

    
    
    if (contestant_area2 * 2 <= total_area2) {
        finish(_ok, "Contestant successfully ate at most half.");
    } else {
        finish(_wa, "Contestant ate more than half.");
    }

    return 0;
}


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

using namespace std;


bool isStrictlyConvexCCW(const vector<pair<long long, long long>>& p) {
    int n = p.size();
    if (n < 3) return false;
    for (int i = 0; i < n; i++) {
        pair<long long, long long> p1 = p[i];
        pair<long long, long long> p2 = p[(i + 1) % n];
        pair<long long, long long> p3 = p[(i + 2) % n];
        
        long long dx1 = p2.first - p1.first;
        long long dy1 = p2.second - p1.second;
        long long dx2 = p3.first - p2.first;
        long long dy2 = p3.second - p2.second;
        
        long long cross = dx1 * dy2 - dy1 * dx2;
        
        if (cross <= 0) return false; 
    }
    return true;
}

int main(int argc, char* argv[]) {
    registerGen(argc, argv, 1);
    
    
    
    string mode = opt<string>("mode", "non");
    int n = opt<int>("n", -1);
    
    
    if (n == -1) {
        
        if (rnd.next(3) == 0) n = rnd.next(4, 20);
        else n = rnd.next(21, 100);
    }
    
    const int MAX_COORD = 1000000;
    const double PI = acos(-1.0);
    
    vector<pair<long long, long long>> poly;
    
    
    
    while (true) {
        poly.clear();
        
        
        
        
        
        
        int shapeType = rnd.next(4);
        double rx, ry;
        double minR = 100000.0; 
        
        if (shapeType == 0) {
            rx = ry = rnd.next(minR, (double)MAX_COORD - 5000.0);
        } else if (shapeType == 1) {
            rx = rnd.next(minR, (double)MAX_COORD - 5000.0);
            ry = rnd.next(minR / 2.0, rx / 2.0);
        } else if (shapeType == 2) {
            ry = rnd.next(minR, (double)MAX_COORD - 5000.0);
            rx = rnd.next(minR / 2.0, ry / 2.0);
        } else {
            rx = rnd.next(minR, (double)MAX_COORD - 5000.0);
            ry = rnd.next(minR, (double)MAX_COORD - 5000.0);
        }
        
        
        double rot = rnd.next(0.0, 2.0 * PI);
        double cos_rot = cos(rot);
        double sin_rot = sin(rot);
        
        
        vector<double> gaps;
        double total_gap = 0;
        for(int i = 0; i < n; ++i) {
            double g = rnd.next(1.0, 10.0); 
            gaps.push_back(g);
            total_gap += g;
        }
        
        double current_ang = rnd.next(0.0, 2.0 * PI);
        vector<double> angles;
        for(int i = 0; i < n; ++i) {
            angles.push_back(current_ang);
            current_ang += (gaps[i] / total_gap) * 2.0 * PI;
        }
        
        
        bool ok = true;
        for(double ang : angles) {
            double lx = rx * cos(ang);
            double ly = ry * sin(ang);
            
            
            double tx = lx * cos_rot - ly * sin_rot;
            double ty = lx * sin_rot + ly * cos_rot;
            
            long long x = round(tx);
            long long y = round(ty);
            
            
            if (x > MAX_COORD) x = MAX_COORD;
            if (x < -MAX_COORD) x = -MAX_COORD;
            if (y > MAX_COORD) y = MAX_COORD;
            if (y < -MAX_COORD) y = -MAX_COORD;
            
            
            if (!poly.empty() && poly.back().first == x && poly.back().second == y) {
                ok = false; break;
            }
            
            if (poly.size() == n - 1 && poly[0].first == x && poly[0].second == y) {
                ok = false; break;
            }
            
            poly.push_back({x, y});
        }
        
        if (!ok || poly.size() != n) continue;
        
        
        if (isStrictlyConvexCCW(poly)) {
            break;
        }
    }
    
    
    
    cout << n << "\n";
    for(const auto& p : poly) {
        cout << p.first << " " << p.second << "\n";
    }
    
    return 0;
}