Relajación lagrangiana
Recursos
| Fuente | Recurso | Notas |
|---|---|---|
| Mamnoon Siam | Attack on Aliens | |
| Serbanology | The Trick from Aliens |
Relajación lagrangiana
La relajación lagrangiana (Lagrangian Relaxation) consiste en transformar una restricción sobre una variable en un costo y buscar de forma binaria el óptimo.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| NOI.sg | 2019 - Feast | Fácil | Solución |
El problema nos da un arreglo de longitud () de enteros en el rango . Se nos da un () y se pide elegir a lo sumo 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 por crear un subarreglo nuevo (es decir, cada vez que creamos un subarreglo penalizamos nuestra suma en ).
Esto nos lleva al subproblema de hallar la suma máxima y el número de subarreglos usados si crear un subarreglo nuevo cuesta . Podemos resolverlo en con programación dinámica.
Solución de programación dinámica
Definimos como la suma máxima si consideramos los primeros elementos, dado que indica si el elemento forma parte de un subarreglo. Sea el número de personas usadas en un arreglo óptimo de .
Para las transiciones de , tenemos
y
porque o bien empezamos un subarreglo nuevo o bien continuamos uno existente.
Sea la suma máxima alcanzable con penalización y el número de subarreglos usados para lograr . Entonces la suma máxima posible si usamos exactamente subarreglos es . Observar que sumamos para deshacer la penalización.
Nuestro objetivo es hallar algún tal que (asumiendo que es a lo sumo el número de elementos positivos). A medida que aumentamos , tiene sentido que disminuya porque estamos penalizando más los subarreglos. Así, podemos intentar buscar de forma binaria un que haga y tomar como respuesta en el óptimo.
Esta idea casi funciona, pero todavía hay algunas advertencias y condiciones muy importantes que no hemos considerado.
Geometría
Sea la suma máxima si usamos a lo sumo subarreglos. Queremos hallar .
La primera condición es que debe ser cóncava o convexa. Como es creciente en este problema, esto significa que necesitamos que sea cóncava: . 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 , sumidero y vértices adicionales numerados de a . Tendremos las siguientes aristas.
-
Una arista dirigida de a () con peso y capacidad .
-
Una arista dirigida de () a con peso y capacidad .
-
Una arista bidireccional de () a con peso y capacidad .
será el -flujo de costo máximo a través del grafo. Podemos hallar repetidamente el camino aumentante de costo máximo veces para obtener la respuesta. Como el costo máximo como función del flujo es cóncavo, será cóncava. Se puede leer más sobre simular flujos de costo aquí .
Consideremos las siguientes gráficas de y . En este ejemplo, tenemos .
Aquí entra el hecho de que es cóncava. Como la pendiente no es creciente, sabemos que primero crece, luego se mantiene igual y finalmente decrece.
Sea la suma máxima óptima alcanzable con penalización y el número de subarreglos usados para lograr (notar que si hay varias posibilidades, fijamos como el número máximo de subarreglos para lograr ). Estos valores se pueden calcular en con el enfoque de programación dinámica descrito arriba.
Cuando asignamos la penalización , estamos intentando hallar la suma máxima si crear un subarreglo reduce nuestra suma en . Así, será el máximo de y será igual al más a la derecha que maximiza .
Dada la forma de , sabemos que se maximiza en todos los puntos donde es igual a la pendiente de (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 . Así, será el más a la derecha en el que la pendiente de sigue siendo mayor o igual que .
Ahora sabemos exactamente qué representa : es la pendiente y es el más a la derecha en el que la pendiente de sigue siendo mayor o igual que .
Buscamos de forma binaria y hallamos el mayor tal que . Sea el valor óptimo . Entonces nuestra respuesta es . Observar que esto funciona incluso si , porque en ese caso y estarán sobre la misma recta de pendiente .
Como calcular y con la solución de programación dinámica descrita arriba toma , esta solución corre en .
#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
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Platinum | Tall Barn | Fácil | — | ||
| CF | New Year & Handle Change | Normal | — | ||
| CF | ★ Teleporters | Normal | — | ||
| FHC | Vacation | Normal | — | ||
| Kattis | Blazing New Trails | Normal | Solución | ||
| Balkan OI | ★ 2019 - Tennis | Normal | — | ||
| IOI | 2016 - Aliens | Difícil | Solución |