Problema "DQUERY"

Hola! Me puse a resolver el problema “DQUERY” del 5to envío. Quería ver si alguien me daba una mano optimizando mi solución, porque SPOJ me devuelve TLE y no puedo saber qué puntaje saqué.

Dejo mi código y explico un poco lo que hice:

Código
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>

#define forn(i,N) for(int i=0;i<N;i++)

using namespace std;

struct query {

    int a, b, ini, res;
};

int N, M, Q; /// N numbers, M bucket size, Q queries

int sequence[40000];
vector <query> queries;

map <int,int> elements;

bool byPos(query x, query y){
    return x.ini < y.ini;
}

bool moSort(query x, query y){

    if (x.a/M < y.a/M){
        return true;
    }

    if (x.a/M == y.a/M){
        if (x.b < y.b){
            return true;
        } else {
            return false;
        }
    }

    return false;
}

void addElement(int pos){

    if (elements.count(sequence[pos])){
        elements[ sequence[pos] ] += 1;
    } else {
        elements[ sequence[pos] ] = 1;
    }
}

void removeElement(int pos){

    elements[ sequence[pos] ] -= 1;
    if (!elements[sequence[pos]]){
        elements.erase(sequence[pos]);
    }
}

void processQueries(){

    int x=1, y=1;
    forn(i,Q){

        query q = queries[i];

        if (!i){

            x=q.a; y=q.a;

            while (y<=q.b){
                addElement(y);
                y++;
            }
            y--;

        } else {

            while (y<q.b){
                y++;
                addElement(y);
            }

            while (y>q.b){
                removeElement(y);
                y--;
            }

            while (x<q.a){
                removeElement(x);
                x++;
            }

            while (x>q.a){
                x--;
                addElement(x);
            }
        }

        queries[i].res = elements.size();
    }
}

int main(){

    cin >> N;
    forn(i,N){
        cin >> sequence[i+1];
    }

    cin >> Q;
    forn(i,Q){
        int buffA, buffB;
        cin >> buffA >> buffB;

        queries.push_back({buffA,buffB,i,0});
    }

    M = sqrt(Q);
    sort(queries.begin(),queries.end(),moSort);

    processQueries();

    sort(queries.begin(),queries.end(),byPos);
    forn(i,Q){
        cout << queries[i].res << endl;
    }

    return 0;
}

Lo que hice fue usar Mo’s Algorithm para ordenar las Q queries para poder responderlas de forma más eficiente. Tengo dos punteros dentro de mi secuencia de números, uno ‘x’ para el comienzo de mi “ventana” y otro ‘y’ para el final. Esta ventana la represento con un map que usa los números de la secuencia como claves, y la cantidad de apariciones dentro de la ventana como valores.

Al mover ‘y’ hacia la derecha, agrego a mi ventana al elemento sobre el que quedé parado. Si no existía, creo esa clave dentro del map y seteo su valor en 1. Si existía, le sumo 1 al valor de esa clave. Si muevo ‘y’ hacia la izquierda (saco un elemento), le resto 1 al valor de la clave correspondiente al número de la secuencia en donde estaba parado. Si el valor pasa a ser 0, elimino esa clave del map.

Lo mismo para ‘x’, pero al revés. Así, voy agregando o sacando elementos hasta que mis punteros coincidan con la query pedida. El resultado de cada query es el tamaño del map cuando mis punteros ‘x’ e ‘y’ coinciden con el principio y final de la query respectivamente. Cuando terminé de procesarlas todas, las vuelvo a ordenar según su posición original e imprimo el resultado de cada una.

Me gustaría saber cómo puedo agilizar un poco las cosas. No se si es porque hago dos sort, porque estoy ordenando mal las queries, o porque debería usar otra estructura en vez de map (multiset, a lo mejor?).

Saludos!

Todos los números son \leq 10^6, por lo que no hace falta un map para contar las cantidades. Se puede usar un array común. Probá con eso, seguramente te va a dar Accepted.

1 me gusta

Lo que dice Diego está bueno. Es una recomendación usual cuando querés hacer esto de asignar a un número un valor, y esos números “keys” son chicos.
Igualmente, quería comentar hace unos días y venía no pudiendo, que si te fijás en spoj, en los comentarios hablan de “fast I/O” y cosas así. Con tiempos tan justos, y sobre todo cuando tenés que imprimir muchas cosas así como queries (del orden de 10^5 en general), el input/output puede matarte. Es una cagada que sea así, pero pasa.

Vas a ver que aunque uses la recomendación de Diego te va a seguir dando TLE, hasta que cambies cin/cout por scanf/printf , o agregues el sync_with_stdio y cin.tie y cambies endl por “\n”.

No encontré en la wiki algo sobre input/output pero por ahora si querés podés ir viendo esta clase del training camp del año pasado: http://trainingcamp.org.ar/anteriores/2017/clases/01-fast-io.pdf

Aparte de eso, felicitaciones por el problema! El código me gustó, y de hecho que hayas hecho las funciones addElement y removeElement hace que sea re fácil implementar la ayuda de Diego.

1 me gusta

Gracias por las respuestas! Agregué los dos cambios y dio accepted!
Para pasar de map a array tuve que cambiar una o dos cositas, quedó así:

Código
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

#define forn(i,N) for(int i=0;i<N;i++)

using namespace std;

struct query {

    int a, b, ini, res;
};

int N, M, Q; /// N numbers, M bucket size, Q queries

int sequence[400000];
vector <query> queries;

int elements[2000000];
int bagSize=0;

bool byPos(query x, query y){
    return x.ini < y.ini;
}

bool moSort(query x, query y){

    if (x.a/M < y.a/M){
        return true;
    }

    if (x.a/M == y.a/M){
        if (x.b < y.b){
            return true;
        } else {
            return false;
        }
    }

    return false;
}

void addElement(int pos){

	if (!elements[ sequence[pos] ]){
		bagSize++;
	}
    elements[ sequence[pos] ] += 1;
    
}

void removeElement(int pos){

    elements[ sequence[pos] ] -= 1;
    if (!elements[ sequence[pos] ]){
    	bagSize--;
    }
}

void processQueries(){

    int x=1, y=1;
    forn(i,Q){

        query q = queries[i];

        if (!i){

            x=q.a; y=q.a;

            while (y<=q.b){
                addElement(y);
                y++;
            }
            y--;
        } else {

            while (y<q.b){
                y++;
                addElement(y);
            }
            while (y>q.b){
                removeElement(y);
                y--;
            }
            while (x<q.a){
                removeElement(x);
                x++;
            }
            while (x>q.a){
                x--;
                addElement(x);
            }
        }

        queries[i].res = bagSize;
    }
}

int main(){

    scanf("%d",&N);
    forn(i,N){
        scanf("%d",&sequence[i+1]);
    }

    scanf("%d",&Q);
    forn(i,Q){
        int buffA, buffB;
        scanf("%d%d", &buffA, &buffB);

        queries.push_back({buffA,buffB,i,0});
    }

    M = sqrt(Q);
    sort(queries.begin(),queries.end(),moSort);

    processQueries();

    sort(queries.begin(),queries.end(),byPos);
    forn(i,Q){
        printf("%d\n", queries[i].res);
    }

    return 0;
}

El map “elements” pasa a ser un array. Cuando agrego un elemento, le sumo 1 al casillero que representa su valor. Si era 0, estoy agregando un número nuevo entonces le sumo 1 al tamaño de la “bolsa”. Cuando saco un elemento, le resto 1a ese mismo casillero. Si queda en 0, eliminé completamente un número de mi “bolsa” entonces le resto 1 a su tamaño.

La verdad que las funciones auxiliares te hacen la vida más fácil.

Muchas gracias a los dos!

2 Me gusta