Skip to Content

Relajación lagrangiana

Recursos

Recursos
FuenteRecursoNotas
Mamnoon SiamAttack on Aliens
SerbanologyThe Trick from Aliens

Relajación lagrangiana

La relajación lagrangiana (Lagrangian Relaxation) consiste en transformar una restricción sobre una variable en un costo λ\lambda y buscar de forma binaria el λ\lambda óptimo.

HechoFuenteNombreDificultadTagsSolución
NOI.sg2019 - FeastFácilSolución

El problema nos da un arreglo de longitud NN (1N31051 \le N \le 3 \cdot 10^5) de enteros en el rango [109,109][-10^9,10^9]. Se nos da un KK (1KN1 \le K \le N) y se pide elegir a lo sumo KK subarreglos disjuntos de modo que se maximice la suma de los elementos incluidos en algún subarreglo.

Intuición

El cuello de botella principal de cualquier solución de programación dinámica para este problema es tener que guardar cuántos subarreglos hemos creado hasta ahora.

Intentemos evitar eso. En lugar de guardar el número de subarreglos creados, asignamos una penalización de λ\lambda por crear un subarreglo nuevo (es decir, cada vez que creamos un subarreglo penalizamos nuestra suma en λ\lambda).

Esto nos lleva al subproblema de hallar la suma máxima y el número de subarreglos usados si crear un subarreglo nuevo cuesta λ\lambda. Podemos resolverlo en O(N)\mathcal{O}(N) con programación dinámica.

Solución de programación dinámica

Definimos dp[i][j:{0,1}]\texttt{dp}[i][j:\{0,1\}] como la suma máxima si consideramos los primeros ii elementos, dado que j=0/1j=0/1 indica si el elemento ii forma parte de un subarreglo. Sea cnt[i][j]\texttt{cnt}[i][j] el número de personas usadas en un arreglo óptimo de dp[i][j]\texttt{dp}[i][j].

Para las transiciones de dp\texttt{dp}, tenemos

{dp[i][0],cnt[i][0]}=max{{dp[i1][0],cnt[i1][0]}{dp[i1][1],cnt[i1][1]} \{\texttt{dp}[i][0], \texttt{cnt}[i][0]\} = \max\begin{cases} \{\texttt{dp}[i - 1][0], \texttt{cnt}[i - 1][0]\}\\ \{\texttt{dp}[i - 1][1], \texttt{cnt}[i - 1][1]\} \end{cases}

y

{dp[i][1],cnt[i][1]}=max{{dp[i1][0]+A[i]λ,cnt[i1][0]+1}{dp[i1][1]+A[i],cnt[i1][1]} \{\texttt{dp}[i][1], \texttt{cnt}[i][1]\} = \max\begin{cases}\{\texttt{dp}[i - 1][0] + A[i] - \lambda, \texttt{cnt}[i - 1][0] + 1\}\\ \{\texttt{dp}[i - 1][1] + A[i], \texttt{cnt}[i - 1][1]\}\end{cases}

porque o bien empezamos un subarreglo nuevo o bien continuamos uno existente.

Sea vv la suma máxima alcanzable con penalización λ\lambda y cc el número de subarreglos usados para lograr vv. Entonces la suma máxima posible si usamos exactamente cc subarreglos es v+λcv+\lambda c. Observar que sumamos λc\lambda c para deshacer la penalización.

Nuestro objetivo es hallar algún λ\lambda tal que c=Kc=K (asumiendo que KK es a lo sumo el número de elementos positivos). A medida que aumentamos λ\lambda, tiene sentido que cc disminuya porque estamos penalizando más los subarreglos. Así, podemos intentar buscar de forma binaria un λ\lambda que haga c=Kc=K y tomar como respuesta v+λcv+\lambda c en el λ\lambda óptimo.

Esta idea casi funciona, pero todavía hay algunas advertencias y condiciones muy importantes que no hemos considerado.

Geometría

Sea f(x)f(x) la suma máxima si usamos a lo sumo xx subarreglos. Queremos hallar f(K)f(K).

La primera condición es que f(x)f(x) debe ser cóncava o convexa. Como f(x)f(x) es creciente en este problema, esto significa que necesitamos que f(x)f(x) sea cóncava: f(x)f(x1)f(x+1)f(x)f(x) - f(x - 1) \ge f(x + 1) - f(x). En otras palabras, cuanto más subarreglos agregamos, menos incrementamos la suma. Intuitivamente se ve que esto es cierto.

Demostración de que la función es cóncava

Construimos un grafo de flujo con fuente SS, sumidero TT y N+1N+1 vértices adicionales numerados de 11 a N+1N+1. Tendremos las siguientes aristas.

  • Una arista dirigida de SS a ii (1iN+11 \le i \le N+1) con peso 00 y capacidad 11.

  • Una arista dirigida de ii (1iN+11 \le i \le N+1) a TT con peso 00 y capacidad 11.

  • Una arista bidireccional de ii (1iN1 \le i \le N) a i+1i+1 con peso A[i]A[i] y capacidad 11.

f(x)f(x) será el xx-flujo de costo máximo a través del grafo. Podemos hallar repetidamente el camino aumentante de costo máximo xx veces para obtener la respuesta. Como el costo máximo como función del flujo es cóncavo, f(x)f(x) será cóncava. Se puede leer más sobre simular flujos de costo aquí .

Consideremos las siguientes gráficas de f(x)f(x) y f(x)λxf(x)-\lambda x. En este ejemplo, tenemos λ=5\lambda=5.

Aquí entra el hecho de que f(x)f(x) es cóncava. Como la pendiente no es creciente, sabemos que f(x)λxf(x) - \lambda x primero crece, luego se mantiene igual y finalmente decrece.

Sea v(λ)v(\lambda) la suma máxima óptima alcanzable con penalización λ\lambda y c(λ)c(\lambda) el número de subarreglos usados para lograr v(λ)v(\lambda) (notar que si hay varias posibilidades, fijamos c(λ)c(\lambda) como el número máximo de subarreglos para lograr v(λ)v(\lambda)). Estos valores se pueden calcular en O(N)\mathcal{O}(N) con el enfoque de programación dinámica descrito arriba.

Cuando asignamos la penalización λ\lambda, estamos intentando hallar la suma máxima si crear un subarreglo reduce nuestra suma en λ\lambda. Así, v(λ)v(\lambda) será el máximo de f(x)λxf(x) - \lambda x y c(λ)c(\lambda) será igual al xx más a la derecha que maximiza f(x)λxf(x) - \lambda x.

Dada la forma de f(x)λxf(x) - \lambda x, sabemos que f(x)λxf(x) - \lambda x se maximiza en todos los puntos donde λ\lambda es igual a la pendiente de f(x)f(x) (estos puntos son rojos en la gráfica de arriba). Si no hay tales puntos, se maximiza en el punto más a la derecha donde la pendiente es menor que λ\lambda. Así, c(λ)c(\lambda) será el xx más a la derecha en el que la pendiente de f(x)f(x) sigue siendo mayor o igual que λ\lambda.

Ahora sabemos exactamente qué representa λ\lambda: λ\lambda es la pendiente y c(λ)c(\lambda) es el xx más a la derecha en el que la pendiente de f(x)f(x) sigue siendo mayor o igual que λ\lambda.

Buscamos de forma binaria λ\lambda y hallamos el mayor λ\lambda tal que c(λ)Kc(\lambda) \ge K. Sea el valor óptimo λopt\lambda_{\texttt{opt}}. Entonces nuestra respuesta es v(λopt)+λoptKv(\lambda_{\texttt{opt}}) + \lambda_{\texttt{opt}} K. Observar que esto funciona incluso si c(λopt)Kc(\lambda_{\texttt{opt}}) \neq K, porque en ese caso c(λopt)c(\lambda_{\texttt{opt}}) y KK estarán sobre la misma recta de pendiente λopt\lambda_{\texttt{opt}}.

Como calcular v(λ)v(\lambda) y c(λ)c(\lambda) con la solución de programación dinámica descrita arriba toma O(N)\mathcal{O}(N), esta solución corre en O(NlogA[i])\mathcal{O}(N\log{\sum A[i]}).

#include <bits/stdc++.h> using namespace std; #define ll long long int main() { int n, k; cin >> n >> k; int a[n]; for (int &i : a) { cin >> i; } /** * @return the maximum sum along with the number of subarrays used * if creating a subarray penalizes the sum by "lmb" and * there is no limit to the number of subarrays you can create */ auto solve_lambda = [&](ll lmb) { pair<ll, ll> dp[n][2]; dp[0][0] = {0, 0}; dp[0][1] = {a[0] - lmb, 1}; for (int i = 1; i < n; i++) { dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = max(make_pair(dp[i - 1][0].first + a[i] - lmb, dp[i - 1][0].second + 1), make_pair(dp[i - 1][1].first + a[i], dp[i - 1][1].second)); } return max(dp[n - 1][0], dp[n - 1][1]); }; ll lo = 0; ll hi = 1e18; while (lo < hi) { ll mid = (lo + hi + 1) / 2; solve_lambda(mid).second >= k ? lo = mid : hi = mid - 1; } cout << solve_lambda(lo).first + lo * k << endl; }

Problemas

HechoFuenteNombreDificultadTagsSolución
PlatinumTall BarnFácil
CFNew Year & Handle ChangeNormal
CFTeleportersNormal
FHCVacationNormal
KattisBlazing New TrailsNormalSolución
Balkan OI2019 - TennisNormal
IOI2016 - AliensDifícilSolución