Translate

martes, 22 de mayo de 2012

Problema B: Adding New Machine (ACM Dalian Regional 2011)


Problema B: Adding New Machine

Les comparto un problema con su explicación del concurso de Dalian en China.

Descripción

  Increíble loca Compañía progresista ( ICPC por sus siglas en ingles), sufre demasiado por su lenta producción. Después de una investigación, ellos encontraron que el embotellamiento estaba en el Absulutely Crowded Manufactory (ACM). Para acelerar el procedimiento, ellos compraron otra maquina para el ACM. Pero un nuevo problema apareció, ¿como poner esta nueva maquina en el ACM?.

   ACM es un complejo rectangular, que esta dividido en W*H celdas. Existen N viejas maquinas rectangulares,  y la nueva maquina no puede ocupar ninguna celda donde existan maquinas viejas. La nueva maquina necesita M celdas consecutivas. Celdas consecutivas significan una línea de celdas adyacentes. Tu tarea es calcular el numero de formas diferentes en las que se pueden colocar la nueva maquina.

Entrada

   Hay múltiples casos de prueba ( no mas de 50). La primera línea de cada caso de prueba contiene 4 enteros W, H, N , M ( 1 <= W, H  <= 10^7 , 0 <= N <= 50000, 1 <= M <= 1000), indicando el ancho y el largo de el ACM , el numero de maquinas viejas  y el tamaño de la nueva maquina. Las siguientes N líneas, cada una contiene 4 enteros Xi1, Yi1 , Xi2 y Yi2 ( 1 <= Xi1 <= Xi2 <= W, 1 <= Yi1 <= Yi2 <= H), indicando las coordenadas de la i-e sima maquina vieja. Se garantiza que no hay celdas ocupadas por dos maquinas viejas.

Salida

Imprimir el numero de formas de colocar la nueva maquina en el ACM.

Entrada Ejemplo
Salida Ejemplo
3 3 1 2
2 2 2 2
3 3 1 3
2 2 2 2
2 3 2 2
1 1 1 1
2 3 2 3
8
4
3




Solución:


 La primera solución que se nos presenta a simple vista es colocar en todas las posiciones vacías la nueva maquina, y contar cuantas veces se puede colocar esta maquina, el único problema es que W y H son muy grandes, y el costo de hacer esto es W*H*M ( 10^17 ), por lo tanto no es una solución viable,  pero de esta solución podemos sacar algunos conclusiones que nos pueden ser útiles, por ejemplo cuando exploramos todas las posibilidades solo existen dos maneras de colocar una maquina iniciando en una posiciones (fila, columna), una es colocándola horizontalmente y la otra es verticalmente.

Para resolver un problema complicado primero hay que irnos a lo particular, y de ahí movernos a lo general y es por eso que vamos a resolverlo parte por parte de este problema.

Primero supongamos que solo es una línea sin obstáculos, si la línea es de tamaño H y la longitud de la maquina es M, el numero de formas que podemos colocar M es H – M + 1 , siempre y cuando M sea menor a H.

Si todas las celdas de la matriz estuvieran libres de obstáculos entonces podríamos contar  con facilidad de cuantas maneras podemos colocar esta nueva maquina en toda la matriz.  Suponiendo que la colocamos de manera vertical, podemos calcular cada columna en 10^7 operaciones ( bueno pero como todos es lo mismo podría ser con tan solo una multiplicación para todas las columnas), para en este caso nos conviene realizar columna por columna, en el caso de las filas cuando la maquina se coloca horizontalmente también se puede contar fila por fila, en a lo mas 10^7 operaciones.

Esto es para el caso en que toda están vacías, con una sola resta funciona, pero ¿que pasa cuando existen maquina ya colocadas y cuando son 50000 de ellas?. Para ese caso debemos primero observar un ejemplo, vean la imagen siguiente:







Como pueden ver, ningún rectángulo se empalma, si nosotros vamos de izquierda a derecha, por cada columna vemos que en el a primera columna no hay nada que nos limite, nosotros calculamos cuantas maquinas nuevas podemos colocar en esta columna con una simple resta, pero en la segunda existe dos rectángulos, cuando inician los rectángulos nosotros debemos calcular exactamente los intervalos vacíos para calcular el numero de maquinas a colocar, existen dos intervalos de 1 y 2, como nuestras maquina es de tamaño 2 , entonces podemos colocar  0 y 1 maquinas nuevas, si nosotros nos movemos a la tercera columna el numero de maquina que vamos a colocar es la misma que en la segunda, ya que no hay ningún rectángulo que termine o inicie, pero en la cuarta columna nos encontramos que uno de los rectángulos se termina, y hay que calcular de nuevo la cantidad de maquinas a colocar. En este caso quedaría un intervalo de 5, la cantidad de maquinas que debemos colocar en esta columna es de 4.

  ¿Que es lo que podemos rescatar de esta parte?
  Nosotros podemos ver que solamente hay que calcular las posiciones donde exista un cambio, donde se agregue un nuevo rectángulo o donde se termine uno, en total el máximo numero de posiciones donde pudiera ocurrir es esto son 100,000 ( ya que hay 50,000 rectángulos  por dos ya que es donde inicia un rectángulo y donde termina).

¿Pero como podemos calcular cada columna el numero de nuevas maquina que debemos colocar? La respuesta es utilizar una estructura de datos para contarlas.

¿Qué estructura de datos utilizaríamos para contar los segmentos unidos y restara M a cada uno de ellos?

Un Árbol de Intervalos nos serviría para nuestros propósitos,(Véase el capítulo de estructura de datos avanzadas), el Árbol de Intervalos nos puede ayudar al conteo de estos segmentos, ya que para cada nodo podemos guardar:

1 – numero de maquinas que podemos contar sin contar la de los extremos.
2 – numero de posiciones no marcadas pegadas a la izquierda .
3 – numero de posiciones no marcadas a la derecha.

Supongamos que tenemos una maquina de tamaño 1x2, este es el tamaño de la maquina que queremos insertar, la siguiente figura muestra una fila vacía, sin ningún rectángulo en ella.


Si nosotros queremos insertar una maquina que va de la columna 2 a la columna 6 y otra maquina de la 8 a la 10 quedaría de  esta manera.

En nuestro árbol de intervalos, quedaría de la siguiente manera:




Cado nodo padre puede sacar la información de sus hijos, por ejemplo en el nodo que tiene la información del segmento [1, 8] , puede calcularlo con sus dos hijos, el [1,4] y el [5,8], para calcular la información se sigue los siguientes pasos.

·       Cuadros izquierdos : Son los cuadros izquierdos del hijo izquierdo.
·       Cuadros derechos: Son los cuadros derechos del hijo derecho.
·       Numero de maquinas:  La suma del numero de maquinas de los dos hijos mas la suma de los  cuadros derechos del hijo izquierdo y los cuadros izquierdo del hijo derecho menos M ( M es el tamaño de la nueva maquina), si el resultado es negativo entonces colocar 0.

¿Que pasa cuando uno de los dos hijos nodos esta completamente lleno?

En ese caso el calculo es distinto, el hijo que tiene todos los cuadros llenos, no tiene un lado izquierdo o derecho con cuadros , si no todo el segmento esta completo y tanto el lado izquierdo como derecho son los mismos cuadros.

Para el caso especifico del nodo que representa el segmento [1,4] del ejemplo anterior, que se muestra en la siguiente figura:


Se calcula de la siguiente manera

·       Cuadros izquierdos:  cuadros izquierdos del hijo izquierdo.
·       Cuadros derechos: la suma de los cuadros del hijo derecho mas los cuados derecho del hijo izquierdo.
·       Numero de maquinas: El numero de maquinas del lado izquierdo.

Como pueden ver se tiene que validar si esta completo alguno de sus hijos o si los dos están llenos, en el caso de que alguno este vacío aplica los mismo que en el primer escenario.

¿Pero por que es tan rápido un árbol de intervalos en este problema?

Cuando agregamos un nuevo cuadrado al árbol, iniciamos de la raíz el nodo que representa a todo el intervalo [1,N],  y nos movemos por sus hijos, ya en los hijos nos vamos recursivamente por todos las ramas del árbol hasta el final. Con las siguiente condiciones:


1.     Si el nodo donde estamos su intervalo ya no esta dentro del que queremos hacer una modificación , no seguimos exploras.
2.     Si el nodo donde estamos su intervalo queda absorbido por el intervalo donde vamos a modificar entonces solo modificamos ese nodo, y dejamos una bandera ahí, diciendo que todos los cuadros de este segmento o están usados o están libres, y ya no seguimos con los demás.
3.     Si no pasa ninguna de las anteriores, entramos a los hijos del nodo actual y realizamos el paso 1 y 2.

Y como pueden ver no se explora todo el árbol en cada modificación si no solo una parte de el, que seria dos veces el largo de las ramas y dependiendo del la longitud del segmento seria la longitud de la rama igual a  log( longitud del segmento ).

Para saber después de nuestras inserciones y eliminaciones cuantas maquinas caben en la fila o columna procesando, seria de la siguiente manera:

El numero de maquinas es igual a el numero de maquinas de la raíz mas max( 0,  los cuadros del lado izquierdo – M) + max( 0, los cuadros del lado derecho – M ) , donde M es el tamaño de la maquina.


Nuestra bandera seria un nuevo elemento del árbol.

Cuadro de texto: La complejidad de este algoritmo es la siguiente  O( N  x log max( W, H) ).




Nota: Uno debe notar que cuando M es 1 solo hay que explorándolo vertical o horizontalmente, ya que su rotación vertical u horizontal es la misma, y solo contaríamos el doble.

Código

# include <stdio.h>
# include <string.h>

#define MAXRECTANGLES 50002

int W, H, N, M, nbreakp, memtree;

typedef struct { int X[2], Y[2];} RECTANGLE;
typedef struct { int izq, der, countL, countR, count, delete, fill; } INTERVALTREE;
typedef struct { int coorden, position, accion; } BREAKPOINT;

INTERVALTREE MEM[ MAXRECTANGLES * 30 ];
RECTANGLE R[MAXRECTANGLES + 1];
BREAKPOINT BP[ (MAXRECTANGLES + 1) * 2];

int comparebp( BREAKPOINT *A, BREAKPOINT *B ){
  if(A->coorden != B->coorden ) return A->coorden - B->coorden;
  return B->accion - A->accion;
}

int min(int a, int b ){ return a < b ? a : b; }
int max(int a, int b ){ return a > b ? a : b; }

INTERVALTREE getNode(int punter, int A, int B){
  INTERVALTREE tmp;
  tmp.countL = 0;
  tmp.countR = 0;
  tmp.count = 0;
  tmp.fill = 0;
  tmp.delete = 0;
  if( punter != -1 && MEM[punter].delete == 0 ) return MEM[ punter ];
  if( punter == -1 || MEM[punter].delete == 2 ){
    tmp.countL = B - A + 1;
    tmp.fill = 1;
  }
  return tmp;
}

void insert( int *punter, int A, int B, int a, int b, int type){
  int med = (A + B ) / 2;
  INTERVALTREE left, rigth;
  if( B < a || b < A ) return;
  if(*punter == -1 ){
     *punter = memtree++;
     MEM[*punter].izq = -1;
     MEM[*punter].der = -1;
     MEM[*punter].count = 0;
     MEM[*punter].countR = 0;
     MEM[*punter].fill = 1;
     MEM[*punter].countL = ( B - A + 1);
     MEM[*punter ].delete = 0;
  }else
     if( MEM[*punter ].delete >= 1){
       if( MEM[*punter].izq != -1) MEM[MEM[*punter].izq].delete = MEM[*punter ].delete;
       if( MEM[*punter].der != -1) MEM[MEM[*punter].der].delete = MEM[*punter ].delete;
       MEM[*punter ].delete = 0;
     }
  if( a <= A && B <= b ){
     MEM[*punter].countL = (  type == 0 ? 0 : B - A + 1);        
     MEM[*punter].fill = type;  
     MEM[*punter].countR = 0;
     MEM[*punter].count = 0;
     if( MEM[*punter].izq != -1) MEM[MEM[*punter].izq].delete = 1 + type;
             if( MEM[*punter].der != -1) MEM[MEM[*punter].der].delete = 1 + type;
     return;
  }
  insert(&MEM[*punter].izq, A, med, a, b, type);
  insert(&MEM[*punter].der, med+1, B, a, b, type);
  /* Calcular */
  left = getNode(MEM[*punter].izq , A, med);
  rigth = getNode(MEM[*punter].der, med +1 , B );
  if( left.fill == 1 && rigth.fill == 1 ){
    MEM[*punter].fill = 1;
    MEM[*punter].countL = B - A + 1;
            MEM[*punter].countR = 0;     
            MEM[*punter].count = 0;
  }else if( left.fill == 1 ){
       MEM[ *punter].fill = 0;
       MEM[*punter].countL = left.countL + rigth.countL;
       MEM[*punter].countR = rigth.countR;
       MEM[*punter].count= rigth.count;
  }else if( rigth.fill == 1 ){
       MEM[ *punter].fill = 0;
       MEM[*punter].countR = left.countR + rigth.countL;
       MEM[*punter].countL = left.countL;
       MEM[*punter].count= left.count;
  }else{
         MEM[*punter].fill = 0;
                 MEM[*punter].countL = left.countL;
                 MEM[*punter].countR = rigth.countR;
                 MEM[*punter].count = left.count+ rigth.count+
                                       max( 0, left.countR + rigth.countL - M + 1) ;
  }
}

int count( int punter, int A, int B){
  if( punter == -1 ) return ( B - A + 1 ) - M + 1;
  if( MEM[punter].delete == 2 ) return ( B - A + 1 ) - M + 1;
  if( MEM[punter].delete == 1 ) return 0;
  return MEM[punter].count + max( 0 , MEM[punter].countL - M + 1) + max( 0, MEM[punter].countR - M + 1);     
}

long long process( int axis , int width, int length){
   int lastpos = 1, cpos, indice = -1, A, B, pos;
   long long currentCont, response = 0;
   memtree = 0;
   for( pos = 0; pos < nbreakp; pos++){
             if( lastpos != BP[pos].coorden ){
       currentCont = (long long)count( indice, 1, length);
       response += currentCont * (long long)( BP[ pos ].coorden - lastpos);
             }
             cpos = BP[ pos ].coorden;
             A = ( axis == 0 ) ? R[ BP[ pos ].position ].Y[ 0 ] : R[ BP[ pos ].position ].X[ 0 ];
             B = ( axis == 0 ) ? R[ BP[ pos ].position ].Y[ 1 ] : R[ BP[ pos ].position ].X[ 1 ];
             insert( &indice , 1, length, A, B, BP[ pos ].accion );
             lastpos = cpos;
   }
   currentCont = (long long)count( indice, 1, length);
   response += currentCont * (long long)( width - lastpos + 1);
  
   return response;
}

main(){
  int rec, nc;
  long long cont;
  while( scanf("%d %d %d %d", &W, &H, &N, &M) != EOF ){
    for( rec = 0; rec < N; rec++)
       scanf("%d %d %d %d", &R[ rec ].X[0], &R[ rec ].Y[0], &R[ rec ].X[1], &R[ rec ].Y[1]);
    for( nbreakp = rec = 0; rec < N; rec++){
       for(nc = 0; nc < 2; nc++){
         BP[ nbreakp  ].position = rec;
         BP[ nbreakp  ].accion = nc;
         BP[ nbreakp++].coorden = R[rec].X[ nc ] + nc;
       }
    }
    qsort(BP, nbreakp, sizeof( BREAKPOINT ), comparebp );
    cont = process( 0, W , H);
    for( nbreakp = rec = 0; rec < N; rec++){
       for(nc = 0; nc < 2; nc++){
         BP[ nbreakp  ].position = rec;
         BP[ nbreakp  ].accion = nc;
         BP[ nbreakp++].coorden = R[rec].Y[ nc ] + nc;
       }
    }
    qsort(BP, nbreakp, sizeof( BREAKPOINT ), comparebp );
    if( M > 1 )
      cont += process( 1, H, W);
    printf("%lld\n", cont);
  }
  return 0;       
}




Cualquier duda o sugerencia escribirme a rodrigo.burgos@gmail.com















No hay comentarios: