Skip to Content

Professional Layer

Análisis oficial (C++) 

Pista 1

Sea gg el GCD de los valores del arreglo, gcd(a1,,an)\gcd(a_1, \dots, a_n). ¿Cuál es la cota sobre el número de primos en la factorización de gg?

Pista 2

Si jugamos un juez, deberíamos intentar eliminar un conjunto específico de primos de su valor de aia_i en una partida.

Sea mm el número de primos en la factorización de gg. Como m11m \leq 11, podemos considerar DP con máscaras de bits, y potencialmente una DP de fusión de subconjuntos O(3M)\mathcal{O}(3^M).

Pista 3

Con N106N \leq 10^6, ejecutar una DP por juez es demasiado lento. ¿Cómo podemos comprimir el número de jueces a una cantidad más manejable?

Pista 4

Para cada valor de aia_i, quitamos todos los factores primos que no están en la factorización de gg. Con las cotas dadas, hay a lo sumo 1200012000 valores distintos de aia_i en cualquier entrada. Como cada juez que juguemos debería quitar al menos un primo de gg, solo consideramos los mm jueces más baratos para cada valor de aia_i.

Sin embargo, hacer de forma naive una DP de fusión de subconjuntos en cada uno corre en O(MK3M)\mathcal{O}(MK \cdot 3^M), donde KK es el número de valores distintos de aia_i. ¿Cómo podemos reducir el número de transiciones de DP a considerar?

Solución

Explicación

Sea gg el GCD de los valores del arreglo, pp el conjunto de primos en la factorización de gg, y m=pm = |p|. Si g=1g=1, entonces la respuesta es 00.

Si jugamos un juez, deberíamos eliminar un subconjunto específico de primos de pp de su valor de aia_i. Como el producto de los primeros 1212 primos ya supera 101210^{12}, tenemos

m11, m\leq 11,

lo que motiva un enfoque con máscaras de bits.

Una DP naive con máscaras tendría dp[i][mask]\text{dp}[i][\text{mask}] como la suma mínima de valores de experiencia para quitar todos los primos en mask\text{mask} con ii jueces. Esto requeriría actualizar nuestra tabla de DP por cada juez, lo que da una complejidad temporal de O(NM3M)\mathcal{O}(NM \cdot 3^M), que es demasiado lenta para este problema.

Comprimir jueces

Para cada valor de aia_i, quitamos todos los factores primos que no están en la factorización de gg, ya que no afectan la respuesta. Después de quitar los primos innecesarios, hay a lo sumo 1200012000 valores distintos de aia_i en cualquier entrada.

Calcular esta cota

En el peor caso, gg es igual al producto de los primeros xx primos más pequeños, donde x11x \leq 11. Esto es porque cualquier valor de aia_i debe compartir los mismos primos que gg y además ser divisible por gg. Por lo tanto, el exponente de cada primo debería ser siempre 11, y los primos deberían minimizarse.

Para derivar la cota de forma rigurosa, hay que iterar sobre todos los valores de xx y calcular el número de valores distintos que son posibles. Se puede mostrar que x=6x=6 y g=30030g=30030 maximiza el número de valores distintos de aia_i, con la cota exacta siendo 1159811598.

Para una estimación un poco más intuitiva, fingimos que todos los primos son equivalentes en valor, aunque siguen siendo distintos. Usamos 55 para este ejemplo.

Si todos los primos equivalen a 55, entonces un número puede estar compuesto de a lo sumo 1717 primos, ya que 518>10125^{18} > 10^{12}. Suponiendo que gg está compuesto de kk factores primos, usamos estrellas y barras para hallar que hay (17k)17 \choose k valores posibles de aia_i. Esto se maximiza cuando k=8k=8, lo que da una estimación de 2431024310.

Aunque esta cota estimada no es precisa, ayuda a mostrar que el número de jueces que consideramos se puede reducir considerablemente desde 10610^6.

Cualquier juez útil debe eliminar al menos un factor primo de gg, así que jugamos a lo sumo mm jueces. Por lo tanto, consideramos a lo sumo mm jueces para cada valor único de aia_i, y siempre es óptimo elegir los de menor experiencia.

Esto reduce el número de jueces a a lo sumo mKmK, y la complejidad de nuestra DP a O(M2K3M)\mathcal{O}(M^2 K \cdot 3^M), donde KK es el número de valores únicos de aia_i.

Optimizar transiciones válidas

Para eliminar un conjunto de primos SS de un valor de aia_i, debemos dividir todas las potencias de primos relevantes. Si el producto de estas potencias de primos es K\leq K, entonces quitar este conjunto de primos de gg es posible.

Formalmente, sea qj=pjvpj(ai)q_j = p_j^{v_{p_j}(a_i)} la potencia completa del primo pjp_j que divide a aia_i. Podemos quitar un conjunto SS si

jSqjK, \prod_{j \in S} q_j \leq K,

ya que este producto es el menor divisor de aia_i que contiene cada primo de SS.

El siguiente código calcula de forma eficiente el costo de quitar cada subconjunto de pp:

std::vector<ll> mask_cost(1 << m, 1); for (int j = 1; j < (1 << m); j++) { // we exclude least significant bit, and calculate contribution // from that bit in our cost to remove it int lsb = __builtin_ctz(j); mask_cost[j] = mask_cost[j ^ (1 << lsb)]; ll val = a[i]; while (val % primes[lsb] == 0) { // since mask_cost grows fast, overflow guard is needed! if (mask_cost[j] > INF / primes[lsb]) break; val /= primes[lsb]; mask_cost[j] *= primes[lsb]; } if (mask_cost[j] <= k) { // process this mask's transition by updating the DP table } }

Como redujimos el número de jueces a considerar, podemos permitirnos calcular todo el costo de quitar subconjuntos para cada juez. Sin embargo, el número de transiciones sigue siendo demasiado grande.

Una vez más, podemos reducir el número de transiciones con la cota sobre el número máximo de jueces. Un estado de transición consiste en (mask,judge)(\text{mask}, \text{judge}), que tiene hasta MK2MMK \cdot 2^M combinaciones. ¡Pero solo necesitamos las mm mejores transiciones para cada máscara de transición! Esto reduce el número de transiciones a O(M2M)\mathcal{O}(M \cdot 2^M).

Implementar la DP

El algoritmo general es el siguiente:

  • Primero, quitamos todos los jueces redundantes, quedándonos con los MM jueces de menor experiencia para cada valor distinto de aia_i. Esto da O(MK)\mathcal{O}(MK) jueces a considerar. Nótese que quitamos todas las potencias de primos que no están en pp de los valores de aia_i.
  • Calculamos todas las máscaras de transición posibles para cada juez restante. Para una máscara de transición posible, solo consideramos este juez si está entre los mm de menor experiencia con esta máscara. Calculamos inv(i), que guarda las máscaras donde el juez ii está entre los mm mejores.
  • Como no podemos reutilizar jueces, iteramos sobre los jueces en orden, actualizando nuestra tabla de DP para las transiciones que cada juez provee.

El siguiente código muestra las transiciones de DP:

// dp[# of judges used][mask] = minimum sum of experiences std::vector dp(m + 1, std::vector<ll>(1 << m, INF)); dp[0][0] = 0; for (int i = 0; i < n; i++) { for (int j = m; j >= 1; j--) { for (int mask : inv[i]) { for (int super = mask; super < (1 << m); super = (super + 1) | mask) { dp[j][super] = std::min(dp[j][super], dp[j - 1][super ^ mask] + e[i]); } } } }

Esto es una implementación de DP de fusión de subconjuntos con máscaras de bits. Aunque una transición individual puede actualizar hasta O(M2M)\mathcal{O}(M \cdot 2^M) estados, la complejidad total se amortiza a O(M23M)\mathcal{O}(M^2 \cdot 3^M).

Con estas optimizaciones, obtenemos un tiempo de ejecución final de O(MK2M+M23M)\mathcal{O}(MK\cdot 2^M + M^2 \cdot 3^M), que corre cómodamente dentro del límite de tiempo si se implementa de forma eficiente.

Implementación

Complejidad temporal: O(MK2M+M23M)\mathcal{O}(MK\cdot 2^M + M^2 \cdot 3^M), donde KK es el número de jueces distintos.

#include <bits/stdc++.h> using ll = long long; constexpr ll INF = 1e18; int main() { std::ios_base::sync_with_stdio(false); std::cin.tie(nullptr); int n; ll k; std::cin >> n >> k; ll g = 0; std::vector<ll> a(n); for (int i = 0; i < n; i++) { std::cin >> a[i]; g = std::gcd(g, a[i]); } std::vector<int> e(n); for (int i = 0; i < n; i++) { std::cin >> e[i]; } if (g == 1) { // we don't need to do anything std::cout << 0 << '\n'; return 0; } // prime factorize the GCD std::vector<ll> primes; ll tmp = g; for (int i = 2; 1LL * i * i <= tmp; i++) { if (tmp % i == 0) { primes.push_back(i); while (tmp % i == 0) { tmp /= i; } } } if (tmp > 1) { primes.push_back(tmp); } const int m = primes.size(); // we process items in sorted order for easier processing of // 'equivalent' judges (judges who cover same transition masks) std::vector<int> ord(n); std::iota(ord.begin(), ord.end(), 0); std::sort(ord.begin(), ord.end(), [&](int x, int y) -> bool { return e[x] < e[y]; }); std::map<ll, int> vis; std::vector<int> seen_masks(1 << m); std::vector<std::vector<int>> inv(n); for (int i : ord) { ll val = a[i]; // calculate the product of prime powers for the // primes in our GCD (since only those primes matter) ll cost = 1; for (int j : primes) { while (val % j == 0) { cost *= j; val /= j; } } // we only use at most m equivalent judges if (vis[cost] >= m) continue; vis[cost]++; // mask_cost[mask] = division value to remove primes in mask from the GCD std::vector<ll> mask_cost(1 << m, 1); for (int j = 1; j < (1 << m); j++) { // we exclude least significant bit, and calculate contribution // from that bit in our cost to remove it int lsb = __builtin_ctz(j); mask_cost[j] = mask_cost[j ^ (1 << lsb)]; val = a[i]; while (val % primes[lsb] == 0) { // since mask_cost grows fast, overflow guard is needed! if (mask_cost[j] > INF / primes[lsb]) break; val /= primes[lsb]; mask_cost[j] *= primes[lsb]; } // for a given transition (i.e. a set of primes we can 'clear'), // we only care about the m cheapest judges for that transition if (mask_cost[j] <= k && seen_masks[j] < m) { seen_masks[j]++; inv[i].push_back(j); } } } // dp[# of judges used][mask] = minimum sum of experiences std::vector dp(m + 1, std::vector<ll>(1 << m, INF)); dp[0][0] = 0; for (int i = 0; i < n; i++) { for (int j = m; j >= 1; j--) { for (int mask : inv[i]) { for (int super = mask; super < (1 << m); super = (super + 1) | mask) { dp[j][super] = std::min(dp[j][super], dp[j - 1][super ^ mask] + e[i]); } } } } ll res = INF; for (int i = 0; i <= m; i++) { if (dp[i][(1 << m) - 1] < INF) { res = std::min(res, dp[i][(1 << m) - 1] * i); } } std::cout << (res < INF ? res : -1) << '\n'; }