Remainder Game
Explicación
Aplicar cualquier módulo dos veces es idéntico a aplicarlo una sola vez, incluso cuando se usan otros módulos, así que debemos comprar cada módulo a lo sumo una vez. Observamos que un módulo grande siempre cuesta más que cualquier combinación de módulos más pequeños, ya que el peor caso es comprar cada módulo más pequeño una vez:
Esto significa que debemos priorizar optimizar el módulo más grande usado a lo largo de todas las transformaciones , lo que motiva nuestra idea voraz: hallar el módulo más grande que debemos comprar, comprarlo exactamente una vez y repetir.
Para ello, construimos un grafo dirigido sobre los vértices . Para cada valor y módulo , construimos una arista con peso . Un camino de a representa una secuencia de operaciones para convertir en , y los módulos necesarios son los pesos de las aristas a lo largo del camino.
Definimos el peso de un camino de a , , como el valor mínimo posible del peso máximo de arista en dicho camino. Así, al procesar una transformación , es el umbral mínimo tal que el camino es factible usando solo operaciones , de modo que debe comprarse en la construcción voraz. Notamos que cuando puede alcanzar en el grafo.
Calculamos estas distancias con una variante de Floyd-Warshall, reemplazando la relajación estándar por:
Como se describió antes, es óptimo comprar el módulo necesario más grande. Para ello, calculamos . Si es imposible transformar algún par de y , imprimimos -1. En caso contrario, compramos y ponemos a toda arista con peso . Notamos que esto exige recalcular más adelante.
Cada iteración compra de forma permanente un módulo, así que hay a lo sumo iteraciones.
Implementación
Complejidad temporal: , donde .
#include <bits/stdc++.h>
using namespace std;
const int MAX_VAL = 50;
const int NUM_NODES = MAX_VAL + 1;
const int INF = 1e9;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n), b(n);
for (int &x : a) cin >> x;
for (int &x : b) cin >> x;
vector<vector<int>> dist(NUM_NODES, vector<int>(NUM_NODES, INF));
for (int i = 0; i <= MAX_VAL; i++) {
for (int j = 0; j <= MAX_VAL; j++) { dist[i][j] = INF; }
dist[i][i] = 0;
}
// Agregamos una arista x -> x % k con peso k; la guardamos para búsqueda rápida después
vector<vector<pair<int, int>>> edges_by_modulus(NUM_NODES);
for (int x = 0; x <= MAX_VAL; x++) {
for (int k = 1; k <= MAX_VAL; k++) {
int y = x % k;
dist[x][y] = min(dist[x][y], k);
edges_by_modulus[k].push_back({x, y});
}
}
long long ans = 0;
while (true) {
auto temp_dist = dist;
// Floyd-Warshall de mínimo-del-máximo bajo los pesos actuales (parcialmente comprados)
for (int mid = 0; mid <= MAX_VAL; mid++) {
for (int i = 0; i <= MAX_VAL; i++) {
for (int j = 0; j <= MAX_VAL; j++) {
temp_dist[i][j] =
min(temp_dist[i][j], max(temp_dist[i][mid], temp_dist[mid][j]));
}
}
}
int largest_required = 0;
for (int i = 0; i < n; i++) {
if (temp_dist[a[i]][b[i]] == INF) {
cout << -1 << "\n";
return 0;
}
largest_required = max(largest_required, temp_dist[a[i]][b[i]]);
}
if (largest_required == 0) break;
ans += (1LL << largest_required);
// Marcamos toda arista que usa este módulo como gratuita para iteraciones futuras
for (auto [from, to] : edges_by_modulus[largest_required]) {
dist[from][to] = 0;
}
}
cout << ans << '\n';
}