Skip to Content

Game With Triangles

Pista 1

El área de un triángulo es 12bh\frac{1}{2}bh, donde bb es la longitud de la base y hh es la altura del triángulo. Nótese que si ponemos la base como el lado con dos puntos, hh es siempre 22. ¿Qué significa esto?

Respuesta a la pista 1

Si elegimos dos puntos xx e yy en un lado, entonces el área del triángulo resultante es simplemente xy|x - y|. ¡La ubicación del tercer punto en el otro lado no importa!

Pista 2

Intentemos diseñar una solución O(n2)\mathcal{O}(n^2) 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 xx pares de puntos en el arreglo aa, e yy pares de puntos en el arreglo bb.

Digamos que queremos hallar la mejor forma de elegir xx pares de puntos en el arreglo aa. ¿Cómo podemos maximizar nuestra área? La forma más directa sería emparejar los xx valores más pequeños con los xx 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 O(n2)\mathcal{O}(n^2): para cada valor de kk, iteramos sobre nuestro valor de xx, con y=kxy = k - x. Con la estrategia voraz de arriba, podemos calcular el mejor resultado para cada par (x,y)(x, y) en tiempo O(1)\mathcal{O}(1).

Sea xx el número de pares que hacemos del arreglo aa, f(x)f(x) el área total máxima de triángulos del arreglo aa, y g(x)g(x) el área total máxima de triángulos del arreglo bb. Entonces, nuestra respuesta es el valor máximo de f(x)+g(x)f(x) + g(x) entre todos los valores válidos de xx.

Podemos hacer las siguientes afirmaciones:

  1. f(x)f(x) es estrictamente creciente, mientras que g(x)g(x) es estrictamente decreciente.
  2. f(x)f(x) y g(x)g(x) tienen diferencias consecutivas no crecientes, lo que las hace convexas.
  3. La función h(x)=f(x)+g(x)h(x) = f(x) + g(x) 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 aa garantiza aumentar el área total de triángulos, asumiendo que todavía podemos emparejar puntos. A la inversa, aumentar xx significa disminuir el número de pares que podemos hacer en el arreglo bb, haciendo g(x)g(x) estrictamente decreciente.

Para la afirmación 2, recordemos cómo nuestra estrategia voraz para hacer xx pares es emparejar los xx puntos más grandes con los xx más pequeños. Si pasamos a hacer x+1x+1 pares, nuestra única diferencia es que agregamos un nuevo par: el (x+1)(x+1)-ésimo valor más pequeño y el (x+1)(x+1)-ésimo más grande. Agregar este par da a lo sumo la misma cantidad de área que agregar el par xx 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 xx son válidos.

Implementación

Complejidad temporal: O(NlogN+MlogM)\mathcal{O}(N\log{N} + M\log{M})

#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]; } } }