Técnicas utilizadas
División y conquista. La estrategia consiste en buscar la respuesta mínima dividiendo el espacio de soluciones. En vez de construir directamente una distribución de cajas, hacemos una pregunta de factibilidad:
Si uso
mcajas apoyadas en el piso, ¿cuál es la máxima cantidad total de cajas que puedo construir?
Si con m cajas de piso puedo construir al menos n cajas en total, entonces m es una
respuesta posible. Si no alcanza, necesito más cajas en el piso. Como esta condición es
monótona, podemos usar búsqueda binaria.
Idea de la solución
Qué estamos buscando
El problema pide la mínima cantidad de cajas que deben tocar el piso para poder acomodar
n cajas respetando la regla de soporte: una caja solo puede ir encima de otra si esa caja de abajo
tiene sus cuatro caras verticales adyacentes a otra caja o a una pared.
En lugar de decidir caja por caja dónde ponerlas, vamos a buscar la respuesta m:
Función de factibilidad
Para aplicar búsqueda binaria necesitamos una función:
puede(m, n)que responda si con m cajas en el piso alcanza para construir n cajas en total.
La clave es calcular:
Si:
entonces m alcanza.
Si:
entonces m no alcanza.
Cómo calcular maxCajas(m)
La mejor forma de usar cajas en el piso es formar capas completas lo más grandes posible.
Cada capa completa de tamaño k necesita en el piso un número triangular:
Y una pirámide completa de tamaño k contiene un número tetraédrico de cajas:
Entonces, para un m dado, buscamos la pirámide completa más grande que pueda apoyarse en
el piso usando como máximo m cajas de piso.
Es decir, buscamos el mayor k tal que:
Después de construir esa pirámide, pueden sobrar cajas de piso:
Esas cajas extra se pueden colocar en una nueva diagonal. Si sobran r cajas de piso, agregan:
cajas adicionales como máximo.
Por lo tanto:
con:
y:
Por qué sirve búsqueda binaria
La función es monótona:
- Si con
mcajas de piso alcanza, entonces conm + 1,m + 2, etc. también alcanza. - Si con
mcajas de piso no alcanza, entonces con menos demtampoco alcanza.
Eso nos permite buscar la mínima respuesta dividiendo el rango en mitades:
- Tomamos un valor medio
mid. - Calculamos si
midalcanza. - Si alcanza, intentamos buscar una respuesta más chica en la mitad izquierda.
- Si no alcanza, buscamos en la mitad derecha.
Código
import math
class Solution:
def minimumBoxes(self, n: int) -> int:
def triangular(k: int) -> int:
return k * (k + 1) // 2
def tetra(k: int) -> int:
return k * (k + 1) * (k + 2) // 6
def max_cajas_con_piso(m: int) -> int:
# Buscamos el mayor k tal que triangular(k) <= m.
# Esta búsqueda interna también es por división y conquista.
izquierda = 0
derecha = m
while izquierda <= derecha:
medio = (izquierda + derecha) // 2
if triangular(medio) <= m:
k = medio
izquierda = medio + 1
else:
derecha = medio - 1
cajas_piso_piramide = triangular(k)
cajas_piso_extra = m - cajas_piso_piramide
return tetra(k) + triangular(cajas_piso_extra)
izquierda = 1
derecha = n
respuesta = n
while izquierda <= derecha:
medio = (izquierda + derecha) // 2
if max_cajas_con_piso(medio) >= n:
respuesta = medio
derecha = medio - 1
else:
izquierda = medio + 1
return respuestaTraza de ejemplo
Instancia n = 13:
| Paso | Cálculo | Resultado |
|---|---|---|
| Rango inicial | izquierda = 1, derecha = 13 | buscamos entre 1 y 13 |
| Medio | mid = 7 | probamos con 7 cajas de piso |
| Mayor pirámide completa | T(3)=6 <= 7, T(4)=10 > 7 | k = 3 |
| Cajas de la pirámide | Tet(3)=10 | 10 cajas |
| Piso extra | 7 - T(3) = 7 - 6 | 1 caja extra de piso |
| Cajas extra | T(1)=1 | 1 caja |
| Total posible | 10 + 1 | 11 cajas, no alcanza |
| Nuevo rango | como 7 no alcanza | buscamos de 8 a 13 |
Probamos mid = 10 | T(4)=10 | Tet(4)=20, alcanza |
| Nuevo rango | como 10 alcanza | buscamos más chico |
Probamos mid = 8 | T(3)=6, sobran 2 | Tet(3)+T(2)=10+3=13 |
| Resultado | 8 alcanza y 7 no alcanza | 8 |
Por lo tanto, para n = 13, la mínima cantidad de cajas apoyadas en el piso es:
Complejidad
Sea n la cantidad total de cajas.
-
Temporal: .
- La búsqueda binaria principal busca la respuesta entre
1yn, por lo que cuesta . - En cada paso calculamos
max_cajas_con_piso(m)usando otra búsqueda binaria para encontrar el mayorkcon , lo que también cuesta . - En total: .
- La búsqueda binaria principal busca la respuesta entre
-
Espacial: .
- Solo usamos variables escalares.
- No se usa memoria adicional proporcional a
n.
Cuándo usar esta técnica
Contexto favorable. Conviene usar esta solución cuando queremos una alternativa no greedy, pero todavía eficiente.
Ventajas.
- No depende de punto flotante.
- No usa raíces cúbicas ni fórmulas cerradas difíciles de implementar.
- Es más fácil de justificar que una solución puramente matemática.
- Es suficientemente eficiente para valores grandes de
n.
Limitaciones.
- Es menos rápida que la solución greedy en forma cerrada .
- Requiere entender la función de factibilidad
max_cajas_con_piso(m). - Aunque no construye la solución caja por caja, todavía depende de las fórmulas triangular y tetraédrica.
Comparación con las otras soluciones del grupo
El grupo implementó dos variantes greedy: una iterativa () y una en forma cerrada (). Ambas construyen la pirámide completa más grande y rellenan el sobrante; se diferencian solo en cómo resuelven cada paso. Esta solución, en cambio, usa división y conquista: no decide de forma voraz cuántas cajas poner, sino que busca la mínima respuesta mediante una condición de factibilidad monótona.
Comparación con cada variante:
- vs. greedy iterativa (). Las dos evitan el punto flotante y son exactas. División y conquista es asintóticamente más rápida (), aunque para la diferencia es imperceptible. La iterativa es un poco más directa de leer.
- vs. greedy en forma cerrada (). La forma cerrada es la más rápida, pero depende de raíces en punto flotante y necesita corrección para no desviarse. División y conquista es más lenta () pero no tiene riesgo numérico y es más fácil de justificar.
Regla práctica:
- Greedy / forma cerrada: mejor rendimiento, a costa de fragilidad numérica.
- Greedy iterativa: exacta y simple, sin punto flotante.
- División y conquista / búsqueda binaria: mejor alternativa no greedy, robusta y clara.
- Programación dinámica: posible como idea, pero no recomendable para las restricciones del problema.