Game With Triangles
Pista 1
El área de un triángulo es , donde es la longitud de la base y es la altura del triángulo. Nótese que si ponemos la base como el lado con dos puntos, es siempre . ¿Qué significa esto?
Respuesta a la pista 1
Si elegimos dos puntos e en un lado, entonces el área del triángulo resultante es simplemente . ¡La ubicación del tercer punto en el otro lado no importa!
Pista 2
Intentemos diseñar una solución al problema con la información de la primera pista.
Solución
Explicación
Como se mencionó en la pista 1, el área de un triángulo solo depende de la longitud de su base, que es el lado donde están dos de nuestros puntos. Así, podemos reducir el problema a elegir pares de puntos en el arreglo , e pares de puntos en el arreglo .
Digamos que queremos hallar la mejor forma de elegir pares de puntos en el arreglo . ¿Cómo podemos maximizar nuestra área? La forma más directa sería emparejar los valores más pequeños con los más grandes en cualquier disposición de emparejamiento. Podemos calcular el área total usando sumas de prefijos de esta forma.
Esto nos da nuestro algoritmo : para cada valor de , iteramos sobre nuestro valor de , con . Con la estrategia voraz de arriba, podemos calcular el mejor resultado para cada par en tiempo .
Sea el número de pares que hacemos del arreglo , el área total máxima de triángulos del arreglo , y el área total máxima de triángulos del arreglo . Entonces, nuestra respuesta es el valor máximo de entre todos los valores válidos de .
Podemos hacer las siguientes afirmaciones:
- es estrictamente creciente, mientras que es estrictamente decreciente.
- y tienen diferencias consecutivas no crecientes, lo que las hace convexas.
- La función es convexa.
¡Intentemos entender por qué cada una de estas afirmaciones es cierta!
Explicación
Para la afirmación 1, agregar otro par de puntos en nuestro arreglo garantiza aumentar el área total de triángulos, asumiendo que todavía podemos emparejar puntos. A la inversa, aumentar significa disminuir el número de pares que podemos hacer en el arreglo , haciendo estrictamente decreciente.
Para la afirmación 2, recordemos cómo nuestra estrategia voraz para hacer pares es emparejar los puntos más grandes con los más pequeños. Si pasamos a hacer pares, nuestra única diferencia es que agregamos un nuevo par: el -ésimo valor más pequeño y el -ésimo más grande. Agregar este par da a lo sumo la misma cantidad de área que agregar el par a nuestra respuesta, haciendo las diferencias consecutivas no decrecientes.
Para la afirmación 3, este es un teorema conocido. La forma más fácil de justificarlo es usar cálculo: en este caso, la convexidad requiere que la segunda derivada sea no positiva. Podemos simplemente aplicar linealidad y ver que sumar múltiples funciones da una segunda derivada que sigue siendo no positiva.
Ahora, como las funciones convexas son unimodales, nuestro problema se reduce a maximizar una función unimodal. Esto se puede manejar con búsqueda ternaria o binaria. Hay que restringir bien el espacio de búsqueda, ya que solo algunos valores de son válidos.
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using ll = long long;
int main() {
int test_num;
std::cin >> test_num;
for (int t = 0; t < test_num; t++) {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n), b(m);
for (int &i : a) { std::cin >> i; }
for (int &i : b) { std::cin >> i; }
// calculate prefix sums for array a
std::sort(std::begin(a), std::end(a));
std::vector<ll> pf_a(n);
pf_a[0] = a[0];
for (int i = 1; i < n; i++) { pf_a[i] = a[i] + pf_a[i - 1]; }
// calculate prefix sums for array b
std::sort(std::begin(b), std::end(b));
std::vector<ll> pf_b(m);
pf_b[0] = b[0];
for (int i = 1; i < m; i++) { pf_b[i] = b[i] + pf_b[i - 1]; }
/** @return best way to choose num pairs in array pf */
const auto get_best = [&](const std::vector<ll> &pf, int num) -> ll {
const int sz = pf.size();
return (pf[sz - 1] - pf[sz - 1 - num]) - (num == 0 ? 0 : pf[num - 1]);
};
// k_max is bounded by the number of points,
// and the number of points on each side
const int k_max = std::min({n, m, (n + m) / 3});
std::cout << k_max << '\n';
for (int i = 1; i <= k_max; i++) {
/** @return best result if we pick j pairs from array a */
const auto get = [&](int j) -> ll {
return get_best(pf_a, j) + get_best(pf_b, i - j);
};
/**
* We use binary search to find the maximum of our function. (See module)
* In this case, because our function strictly increases, reaches its
* maximum, and becomes strictly decreasing, we find the first point where
* the function beceomes strictly decreasing.
*/
int lo = std::max(0, 2 * i - m);
int hi = std::min(i, n - i);
while (lo < hi) {
int mid = (lo + hi) / 2;
if (get(mid) > get(mid + 1)) {
hi = mid;
} else {
lo = mid + 1;
}
}
std::cout << get(lo) << " \n"[i == k_max];
}
}
}