Solución de "Pyramid 2"

Vengo a publicar de vuelta, para ver si alguien me da una mano para mejorar la solución que hice para el problema Pyramid II. Copio mi código y paso a explicar un poco lo que quise hacer:

Código
#include <iostream>
#include <cstdio>
#define forn(i,N) for(int i=0;i<N;i++)

using namespace std;

int pmA, pmB; /// pyramid dimensions
int chC, chD; /// chamber dimensions
int bfM, bfN; /// battlefield dimensions

int field [2000][2000];
int pyramidHash[2000][2000];
int chamberHash[2000][2000];

int result[2][2];

void hashPyramid(){

    int posX=0, posY=0;
    int actualWidth = 0;
    int sum = 0;

    while (actualWidth < pmA ){
        forn(i,pmB){
            sum += field[actualWidth][i];
        }
        actualWidth++;
    }

    pyramidHash[posX][posY] = sum;
    posX++;
    forn(i,(bfN-pmB)+1){

        if (i!=0)
            posX=0;

        if(posY==0){
            forn(j,(bfM-pmA)){

                forn(k,pmB){
                 
                    sum += field[posX+pmA-1][posY+k];
                    sum -= field[posX-1][posY+k];
                }
              
                pyramidHash[posX][posY] = sum;
                posX++;
            }
        }

        int newRowSumUp=0;
        int newRowSumDown=0;
        if(posY>0){

            sum = pyramidHash[posX][posY-1]; 
            forn(k,pmA){ 
                newRowSumUp += field[k][posY-1]; 
                newRowSumDown += field[k][posY+pmB-1];
            }

            pyramidHash[posX][posY] = (sum-newRowSumUp)+newRowSumDown;

            posX++;
            forn(j,(bfM-pmA)){

                sum = pyramidHash[posX][posY-1];

                newRowSumUp += field[posX+pmA-1][posY-1];
                newRowSumUp -= field[posX-1][posY-1];

                newRowSumDown += field[posX+pmA-1][posY+pmB-1];
                newRowSumDown -= field[posX-1][posY+pmB-1];

                pyramidHash[posX][posY] = (sum-newRowSumUp)+newRowSumDown;

                posX++;
            }
        }

        posY++;
    }
}

void hashChamber(){

    int posX=0, posY=0;
    int actualWidth = 0;
    int sum = 0;

    while (actualWidth < chC ){
        forn(i,chD){
            sum += field[actualWidth][i];
        }
        actualWidth++;
    }

    chamberHash[posX][posY] = sum;
    posX++;
    forn(i,(bfN-chD)+1){

        if (i!=0)
            posX=0;

        if(posY==0){
            forn(j,(bfM-chC)){

                forn(k,chD){
                    sum += field[posX+chC-1][posY+k];
                    sum -= field[posX-1][posY+k];
                }
                
                chamberHash[posX][posY] = sum;
                posX++;
            }
        }

        int newRowSumUp=0;
        int newRowSumDown=0;
        if(posY>0){

            sum = chamberHash[posX][posY-1];

            forn(k,chC){

                newRowSumUp += field[k][posY-1];
                newRowSumDown += field[k][posY+chD-1];
            }

            chamberHash[posX][posY] = (sum-newRowSumUp)+newRowSumDown;

            posX++;
            forn(j,(bfM-chC)){

                sum = chamberHash[posX][posY-1];

                newRowSumUp += field[posX+chC-1][posY-1];
                newRowSumUp -= field[posX-1][posY-1];

                newRowSumDown += field[posX+chC-1][posY+chD-1];
                newRowSumDown -= field[posX-1][posY+chD-1];

                chamberHash[posX][posY] = (sum-newRowSumUp)+newRowSumDown;

                posX++;
            }
        }

        posY++;
    }
}

void getResult(){

    int bestRes = 0;

    int maxX = (bfM-pmA)+1;
    int maxY = (bfN-pmB)+1;

    forn(i,maxY){
        forn(j,maxX){

            for (int k=i+1;k<(pmB + i) - (chD);k++){
                for (int l=j+1;l<(pmA + j) - (chC);l++){
                    int res = pyramidHash[j][i] - chamberHash[l][k];
                    if ( res > bestRes ){

                        bestRes = res;
                        result[0][0] = j+1;
                        result[1][0] = i+1;
                        result[0][1] = l+1;
                        result[1][1] = k+1;
                    }
                }
            }
        }
    }
}

int main(){

    cin >> bfM >> bfN >> pmA >> pmB >> chC >> chD;

    forn(i,bfN){
        forn(j,bfM){
            cin >> field[j][i];
        }
    }

    hashPyramid();
    hashChamber();
    getResult();

    cout << result[0][0] << " " << result[1][0] << endl << result[0][1] << " " << result[1][1] << endl;

    return 0;
}

La idea básica del problema es encontrar la posicion de una pirámide y la de una habitación dentro de ella para las cuales la resta entre la altura total de la pirámide menos la altura total de la habitación sea lo mas grande posible.

No se me ocurrió otra forma que probando todas las posibilidades, pero hice un par de cosas para optimizar eso. Primero que nada, “hashee” cada posición posible de la pirámide e hice lo mismo con la habitación, de manera que para cada posición posible se obtiene el valor de la suma de todos los casilleros que contiene. Así, hacer esto:

“… la resta entre la altura total de la pirámide menos la altura total de la habitación …”

tiene una complejidad mucho menor.

Además, el hash lo hice con una ventana deslizante bidimensional. Se comienza obteniendo la altura total de, por ejemplo, la pirámide en la posición (0,0) y se guarda ese valor en una tabla que almacena todos los “hash”. Después, se hashea la pirámide un casillero a la derecha, entonces se toma el hash recién calculado, se le restan los valores de los casilleros que la pirámide ya no ocupa (en este caso, la columna a su izquierda) y se le suman los valores de los casilleros que empieza a ocupar (la columna a su izquierda). Me pareció que de esta manera era mejor que recorriendo todos los casilleros que quiero “hashear”.

Espero que se haya entendido! Esta solución devuelve 66 puntos en el juez de COJ, resolviendo bien todos los casos hasta que tira TimeOut desde un caso en adelante. No hace falta que vean el código, capaz no es muy lindo (y hay código repetido). Pero me gustaría que al menos me encaminen para encontrar una solución mejor (o mejorar la que ya hice).

Muchas gracias!

1 me gusta

Hola Ale. Estás en buen camino por lo que puedo entender. Igual no llamaría “hash” a lo que haces, es simplemente precalcular para cada posición la altura de cada base/cámara en ese punto (pyramidHash -> pyramidHeight).
En efecto, el problema se trata de probar todas las posibilidades pero de forma optimizada. Dada una posible posición de donde poner la pirámide, lo que te falta optimizar es donde poner la cámara. Esto lo estas haciendo con un doble for, pero se puede optimizar. Se puede ver que el problema de donde poner la cámara dada la base de la pirámide se reduce a buscar un mínimo dentro de un rectángulo del arreglo chamberHeight (o chamberHash). Este rectángulo tiene el mismo tamaño sin importar la posición de la pirámide. El objetivo sería entonces precalcular para cada posición de la pirámide, la cámara con menor altura: chamberBest[j][i]. Entonces en tu código te quedaría:
int res = pyramidHash[j][i] - chamberBest[j][i];
Calcular chamberBest es la parte más díficil. Para hacerlo pensalo primero en una dimensión, es decir tenés un arreglo a y para cada posición i querés saber el mínimo entre a[i] y a[i+L-1], con L fijo. Esto lo podes hacer en O(nlgn) con un segment tree, pero no aprovecha el hecho de que L sea fijo. Hay una estructura llamada min-queue que resuelve este problema en O(n): http://www.keithschwarz.com/interesting/code/?dir=min-queue. Te dejo para pensar como llevar esta solución a dos dimensiones.

Espero que te sirva!

2 Me gusta

@DeCicco Fijate que lo que dice Martín también lo podés hacer con una Sliding Window RMQ (que es la temática del envío en el que estaba ese problema).

Sliding Window RMQ lo podés leer de la Wiki.

La sliding window en este caso siempre tiene tamaño L, en cada paso entra uno del final, se va uno del principio, y en todo momento sabés el mínimo de los que están adentro.

1 me gusta

Gracias @Guty ! En la Wiki está explicada muy bien esa parte. Efectivamente mi idea era hacer ese Sliding Window pero se ve que no lo quería quemar completamente jeje.

2 Me gusta

Gracias a los dos!

Para ver si entendí: la idea es armar una deque que contenga los precálculos que hice para las habitaciones. Para cada posición de la pirámide, la mejor posición de su habitación sería dominantes[0]. Al mover la pirámide un casillero, meto en la deque todas las posiciones nuevas que puede tomar la habitación y elimino todas las posiciones que pasan a ser dominadas así como las que dejan de estar dentro de la pirámide.

En cuanto a llevar el problema a dos dimensiones, me parece que si, por ejemplo, muevo la pirámide a la derecha, inserto en la deque todos los casilleros de la nueva columna seguidos, y elimino la misma cantidad del otro extremo.

Espero haber captado bien la idea.

Saludos!

Alto problema. Y sí, hay que hacer un Sliding Window Minimum en 2D sobre los precálculos de las habitaciones.

En mi opinión es más fácil y menos propenso a errores (pero más largo) hacer una función que haga Sliding Window Minimum en 1D y después escalarlo a 2D. Lo que hacés es tomar la matriz y hacerle el SWM_1D a todas las filas, y después, a todas las columnas. Por ejemplo, para una matriz de NxN y una ventana de KxK:

(EDIT: ups. N = 4, K = 2 )

esto:

2 2 4 8
6 5 3 4
4 2 0 3
0 5 4 1

se convierte en esto:

2 2 4
5 3 3
2 0 0
0 4 1

y después en esto (lo que quiero):

2 2 3
2 0 0
0 0 0

Y está copado porque si armás la función 1D para que en cada ventana no utilize los elementos de sus bordes, eso se escala a 2D y te soluciona la parte de que la habitación de la pirámide tiene que estar “abrazada” por la pirámide ( los lados no se pueden tocar ).

2 Me gusta