Skip to Content

Circular Barn

Análisis oficial (C++, Python) 

Explicación

Primero, consideremos el caso N=1N=1, cuando ambos granjeros quitan vacas de una sola habitación.

Cada granjero puede quitar 11, 22, 33, pero no 44 ni ningún múltiplo de 4. Esto nos motiva a considerar a1 mod 4a_1 \bmod 4 en nuestro juego. El jugador que empieza con a1 mod 4≠0a_1\bmod 4\neq 0 debería tomar a1 mod 4a_1\bmod 4 vacas para forzar al otro jugador a a1 mod 4=0a_1\bmod 4=0, quien siempre le dará al primer jugador un a1 mod 4≠0a_1\bmod 4\neq 0, ya que no puede tomar ningún múltiplo de 44 para mantener el resto 00. El primer jugador ahora puede repetir el proceso hasta que a1=0a_1 = 0, lo que resulta en una victoria.

Por tanto, cuando a1 mod 4≠0a_1 \bmod 4 \neq 0, el Granjero John gana. En caso contrario, gana el Granjero Nhoj.

Ahora consideramos el caso N>1N>1. Como cada habitación es independiente, tratamos el juego global como NN juegos individuales. Para cada habitación, definimos al atacante como el ganador garantizado y al defensor como el perdedor garantizado.

El siguiente par de movimientos en una habitación solo se hará cuando se hayan visitado todas las demás habitaciones. Así, los jugadores usan las siguientes estrategias:

  • El atacante de una habitación reduce la cantidad de vacas lo más rápido posible para volver a esta habitación y potencialmente ganar antes.
  • El defensor preferiría que esto se juegue lento para maximizar las chances de ganar temprano en otra habitación. Sea TiT_i la cantidad total de operaciones para terminar la habitación ii de forma individual. El ganador del juego será el ganador de la habitación con el mínimo ⌈Ti2⌉\left\lceil\frac{T_i}2\right\rceil (si hay empate, la de menor número de habitación).

Para minimizar la cantidad de rondas, el atacante puede usar una versión modificada de su estrategia cuando N=1N=1. En lugar de quitar ai mod 4a_i\bmod 4 vacas, puede quitar cualquier cantidad p≡aimod  4p\equiv a_i\mod 4 de vacas siempre que pp sea primo y menor que aia_i. Para calcular el mayor p<xp<x tal que p≡xmod  4p\equiv x\mod 4, primero usamos una criba para hallar todos los primos, y mantenemos un arreglo tit_i con 0≤i<40\leq i<4 para el primo más reciente ti=pt_i=p que encontramos donde p mod 4=ip\bmod 4=i. Al iterar sobre valores de xx, guardamos mx=tx mod 4m_x=t_{x\bmod 4} para representar el primo equivalente más grande.

Por otro lado, el defensor puede bloquear esta aceleración aprovechando que no hay primos p>4p>4 que cumplan p mod 4=2p\bmod 4=2, lo que implica que no hay aceleraciones cuando ai mod 4=2a_i\bmod 4=2. Si el defensor toma 22 vacas de su ai mod 4=2=0a_i\bmod 4=2=0 y devuelve ai mod 4=2a_i\bmod 4=2 cada vez, el atacante ya no podrá acelerar el juego.

Así:

  • Cuando ai mod 4∈{1,3}a_i\bmod4\in\{1,3\}, la única aceleración que el atacante puede aplicar es quitar maim_{a_i} al inicio, lo que da Ti=1+ai−mai2T_i=1+\frac{a_i-m_{a_i}}2.
  • En caso contrario, no hay aceleraciones y Ti=ai2T_i=\frac{a_i}2.

Primero calculamos TiT_i para cada habitación, y luego hallamos el mínimo de ⌈Ti2⌉\left\lceil\frac{T_i}2\right\rceil (si hay empate, con el menor ii), y emitimos el ganador de esa habitación.

Implementación

Complejidad temporal: O(N)\mathcal{O}(N)

#include <cstring> #include <iostream> #include <vector> using namespace std; constexpr int MAX_NUMBER = 5e6; constexpr int MAX_INT = 2e9; int main() { vector<bool> is_prime(MAX_NUMBER + 1, true); // Esto es m[] en la explicación. vector<int> largest_prime_equiv(MAX_NUMBER + 1); is_prime[1] = false; // 1 se considera primo en este problema, aunque // no lo cribemos; así que asignamos 1 al inicio. // Esto es t[] en la explicación. int largest_prime_mod[] = {-1, 1, -1, -1}; largest_prime_equiv[1] = 1; // Algoritmo de criba for (int i = 2; i <= MAX_NUMBER; i++) { if (is_prime[i]) { largest_prime_mod[i % 4] = i; for (int j = i; j < MAX_NUMBER; j += i) is_prime[j] = false; } largest_prime_equiv[i] = largest_prime_mod[i % 4]; } int test_cases; cin >> test_cases; for (int t = 0; t < test_cases; t++) { int n; cin >> n; // Mínimo de turnos, es decir, operaciones / 2 int min_turn = MAX_INT; // Cantidad de operaciones en la habitación de turnos mínimos. int min_turn_ops = MAX_INT; for (int i = 1; i <= n; i++) { int cows; cin >> cows; int operations; if (cows % 2 == 0) { operations = cows / 2; } else { operations = 1 + (cows - largest_prime_equiv[cows]) / 2; } if (operations / 2 < min_turn) { min_turn = operations / 2; min_turn_ops = operations; } } cout << ((min_turn_ops % 2 != 0) ? "Farmer John" : "Farmer Nhoj") << endl; } return 0; }