Skip to Content

Minimizing Difference

Análisis oficial 

Explicación

En lugar de intentar construir directamente el arreglo final, fijamos la diferencia máxima dd y comprobamos si es alcanzable en menos de kk operaciones. Si todos los elementos se pueden mover a algún intervalo de longitud dd, entonces es claro que todos los valores mayores de dd también funcionan. Por lo tanto, la condición es monótona y podemos hacer búsqueda binaria sobre dd.

Para que un arreglo tenga diferencia máxima dd, todos los elementos deben estar en el rango [L,L+d][L, L + d], donde LL es cualquier entero. Esto significa que nuestras operaciones deben mover los valores ai<La_i < L al rango incrementándolos hasta LL, y tratar de forma similar los valores donde ai>L+da_i > L + d.

Si conocemos LL, podemos calcular rápidamente el número de operaciones necesarias para encajar todos los elementos en el rango [L,L+d][L, L + d] usando sumas de prefijos y búsqueda binaria, lo cual detallaremos más adelante.

Elegir L

La intuición indica que fijar LL o L+dL+d a algún valor de aia_i es óptimo. Esto es cierto, y podemos justificarlo de forma informal estableciendo un paralelo con el problema de minimizar sumas, que se explora en CPH 6.4 . Una explicación más detallada está en el spoiler de abajo.

Demostración

La forma más sencilla de mostrarlo es con un argumento de intercambio. En esencia, mostramos que cualquier solución óptima se puede transformar en la construcción voraz sin afectar la calidad de la solución final.

Supongamos que ni LL ni RR (que es igual a L+dL+d) coinciden con ningún valor de aa. Sea pp el número de elementos por debajo de LL y qq el número por encima de RR. Desplazar la ventana una unidad a la derecha cambia el costo total en pqp - q (los elementos por debajo de LL necesitan un paso más cada uno, y los elementos por encima de RR necesitan uno menos). Desplazar a la izquierda cambia el costo en qpq - p por un razonamiento similar.

  • Si pqp \neq q: desplazar en la dirección más barata (la dirección con más elementos) reduce estrictamente el costo total en pq|p - q| por paso, así que la colocación actual no es óptima.
  • Si p=qp = q: desplazar en cualquiera de las dos direcciones deja el costo total igual, así que podemos desplazar libremente.

En ambos casos, podemos seguir desplazando hasta que LL o RR coincida con algún aia_i, sin aumentar nunca el costo. Por lo tanto, cualquier solución óptima se puede transformar en una en la que un extremo coincide con algún valor del arreglo.

Calcular la respuesta final

Sabiendo que LL o L+dL+d debe coincidir con algún valor de aia_i, podemos iterar sobre cada aia_i e intentar calcular el costo asociado a fijarlo como valor de LL o L+dL+d.

Para un rango [L,R][L, R], donde R=L+dR=L+d, el costo de encajar todos los elementos en el rango es:

ai<L(Lai)+ai>R(aiR) \sum_{a_i < L} (L - a_i) + \sum_{a_i > R} (a_i - R)

En otras palabras, sumamos los costos de mover los valores ai<La_i < L hasta igualar LL, y los costos de mover los valores ai>Ra_i > R hasta igualar RR.

Si ordenamos nuestro arreglo aia_i, entonces un prefijo y un sufijo de valores tendrán que moverse para quedar dentro del rango. Usamos búsqueda binaria para hallar el prefijo y el sufijo de valores que hay que ajustar, y sumas de prefijos para sumar los valores relevantes de aia_i.

Implementación

Complejidad temporal: O(NlogNlogA)\mathcal{O}(N \log N \log A), donde AA es el valor máximo de aia_i.

#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; ll k; cin >> n >> k; vector<int> a(n); for (int &x : a) cin >> x; sort(a.begin(), a.end()); // calcular sumas de prefijos, usando indexación desde 1 por comodidad vector<ll> pref(n + 1); for (int i = 0; i < n; i++) { pref[i + 1] = pref[i] + a[i]; } auto check = [&](int d) -> bool { for (int i = 0; i < n; ++i) { // Caso 1: l = a[i] (extremo izquierdo fijado a a[i]) { int l = a[i], r = l + d; // usar sumas de prefijos para calcular l - a_i para todo a_i < l ll cost_l = 1ll * i * l - pref[i]; // usar sumas de prefijos para calcular a_i - r para todo a_i > r // usamos upper bound para hallar el primer índice desde el que sumamos int p = upper_bound(a.begin(), a.end(), r) - a.begin(); ll cost_r = (pref[n] - pref[p]) - 1ll * (n - p) * r; if (cost_l + cost_r <= k) return true; } // Caso 2: l = a[i] - d (extremo derecho fijado a a[i]) { int r = a[i], l = r - d; // usar sumas de prefijos para calcular a_i - r para todo a_i > r ll cost_r = (pref[n] - pref[i + 1]) - 1ll * (n - i - 1) * r; // usar sumas de prefijos para calcular l - a_i para todo a_i < l // usamos lower bound para hallar el último índice desde el que sumamos int p = lower_bound(a.begin(), a.end(), l) - a.begin(); ll cost_l = 1ll * p * l - pref[p]; if (cost_l + cost_r <= k) return true; } } return false; }; int lo = 0, hi = a[n - 1] - a[0]; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (check(mid)) { hi = mid; } else { lo = mid + 1; } } cout << lo << '\n'; }