Cómo resolver Nested Ranges Count de CSES con Segment Tree (en C++)
Esta vez no haré un resumen del eununciado porque ya es muy corto y asumo que sabes inglés si pretendes resolver problemas en CSES. También es necesario conocer lo básico de análisis de complejidad de algoritmos, la STL de C++, y el Segment Tree.
Importante: Los índices de los vectores y otras estructuras de datos están en base
Solución ingenua
Para cada rango, se itera sobre cada uno de los otros y se comprueba si el otro rango contiene al primer rango o viceversa. Como hay
Para comprobar que la solución no es lo suficientemente eficiente hay que considerar que
Pongamos orden
Probemos a ordenar los rangos de menor a mayor valor del límite izquierdo y luego el límite derecho de cada rango (es decir, si dos rangos tienen el mismo límite izquierdo, entonces el que tiene menor límite derecho va primero). Es una técnica muy común para resolver problemas de este tipo. Gracias a ordenar los datos de entrada, sabemos que al iterar por la lista ordenada de rangos, ninguno de los rangos por lo que ya hayamos iterado estará contenido dentro del rango de la iteración actual (llamémoslo rango
Para ejemplificar lo anterior, usemos el caso de prueba de ejemplo pero ordenado:
1 6 2 4 3 6 4 8
Cuando se procese el rango (3, 6), es claro que los rangos (1, 6) y (2, 4) no estarán contenidos en éste.
Entonces, la idea de la solución es iterar por cada uno de los rangos (ahora ordenados) y para cada uno calcular tanto el número de rangos que contiene y el número de rangos que lo contienen.
Ahora sería tentador pensar que para cada rango basta con una búsqueda binaria que nos diga cuántos rangos empiezan entre el
Inclusión-Exclusión
El problema puede hacerse mucho más sencillo si a
- el número de rangos que empiezan antes del
, más - el número de rangos que terminan después del
, menos - el número de rangos que empiezan antes del
y después del .
Lo anterior es una aplicación del Principio de Inclusión-Exclusión, que dados dos conjuntos
A continuación veremos cómo calcular cada uno de los tres números.
Calculemos el 1ero
Gracias al ordenamiento hecho al principio, ya casi tenemos el número de rangos que empiezan antes del
Calculemos el 2do
Después de leer la entrada, creamos otro vector que contenga los límites de los rangos pero de forma invertida, el
Calculemos el 3ero
¿Recuerdas que todos los elementos antes del
Una idea aparentemente útil consiste en crear un vector que sirva como histograma tal que en el índice
Lamentablemente, el intento de solución anterior tiene dos problemas: por cada rango habría que iterar por aproximadamente
Compresión de coordenadas
La compresión de coordenadas consiste en asignar un número distinto a cada número de la entrada para que el rango al que pertenecen los números a manejar sea más reducido. Como hay a lo más
La idea es que el orden relativo de los números se mantenga. Por ejemplo, no queremos asignarle un
Creamos un set al que le insertamos los valores de los rangos a medida que se ingresan por la entrada, un map que contenga las asociaciones de números, y un contador con el número de elementos distintos que llevamos. Después iteramos por cada elemento del set, primero aumentamos el contador en
El proceso anterior se realiza antes de ordenar los vectores, para no gastar tiempo de más.
Ahora la cantidad de memoria consumida por la solución será muchísimo menor a lo que se tenía pensado originalmente. Un problema que parecía imposible se solucionó con un truco sencillo ;)
Aún falta algo
Nos falta calcular el número de rangos que contienen al rango