Problema B - Viagem Alternativa


Tipo de problema: Árvores/Simulação
Autor do Problema: Pedro Paredes (DCC/FCUP) e Pedro Ribeiro (DCC/FCUP)

Problema:
Dado o tamanho N do TIE Bus e um número de mudanças de passageiros Q, a tua tarefa é descobrir onde fica cada um dos passageiros que entra. Uma mudança é uma entrada de um passageiro para um lugar L, um número escolhido P e um sentido de procura s (o sentido procura representa o sentido para onde esse passageiro se desloca, para depois ou antes de L) ou uma saída do passageiro de um lugar L.

Restrições:
1 ≤ N ≤ 500 000       Número de Lugares
1 ≤ Q ≤ 500 000       Número de Mudanças
1 ≤ L, P ≤ N       Lugar de entrada ou de saída e número de lugares que cada passageiro se desloca


Solução base:
Este problema é um típico problema de simulação onde a solução base é efetivamente efetuar a simulação programaticamente. Assim, bastava implementar um algoritmo que para cada entrada, iterava por todos os lugares a partir de L (tendo atenção ao sentido) e contando o número de lugares ocupados, algo que pode ser feito guardando uma matriz do tamanho do Bus e marcando se cada lugar está ou não ocupado. Para uma saída basta marcar um lugar como ocupado nessa matriz.

A complexidade desta solução é igual ao número de movimentos vezes o número de lugares andados por movimento, como em teoria o número de lugares andado por movimento é no máximo N, esta solução tem uma complexidade de O(Q*N).


Ideia para melhorar:
Para melhorar a solução base é preciso reparar que é necessário arranjar uma estrutura que nos permita reter alguma informação para o cálculo de cada movimento. Notem que esta estrutura terá de resolver o problema do RSQ (Range Sum Query), que é o problema de dado um intervalo, dizer a soma de todos os elementos nesse intervalo, que aqui corresponde ao número de lugares desocupados (para os podermos contar). A complexidade de cálcular cada intervalo terá de ser inferior a O(N) (ou teriamos a mesma complexidade que a solução trivial). É também importante que esta estrutura seja dinâmica, pois após resolver cada intervalo é necessário inserir o novo passageiro no seu lugar, mas também porque é necessário retirar passageiros dos seus lugares (quando o movimento é de saida).

Assumindo que temos esta estrutura, podemos agora apenas primeiro considerar que a temos e resolver o resto do problema. O problema agora é reduzido a encontrar um intervalo de L a N (ou de 1 a L, dependendo do sentido, mas vamos assumir sem perda de generalidade o primeiro) cuja soma dos lugares desocupados seja exatamente P (ou então concluir que não existe). Primeiro podemos ver facilmente se não existem P lugares disponíveis, ou seja, que a resposta seria "Saiu": basta ver se a soma dos lugares desocupados entre L e N é menor que P. Se não for, então existe solução.

Assumindo que existe solução, encontrá-la também tem que ser feito em complexidade inferior a O(N). Podemos tirar partido da ordenação dos lugares, isto é, visto que o número de lugares desocupados nunca diminui, se fixarmos um limite do intervalo e aumentarmos o outro (que é uma maneira de dizer que é uma sequência ordenada). Isto significa que podemos usar a técnica de pesquisa binária na resposta que funcionará da seguinte maneira: consideramos um intervalo (que inicialmente é de L a N) com limites a e b = [a, b]. Consideramos o ponto médio do intervalo (onde: ponto médio = (a + b) / 2). Se no intervalo de a ao ponto médio o número de lugares desocupados é superor ou igual a P, então concluímos que a solução está ou no ponto médio ou antes do ponto médio, o que equivale a encontrar o P-ésimo lugar desocupado no intervalo [a, ponto médio]. Se no intervalo de a ao ponto médio o número de lugares desocupados é inferior a P, então concluimos que a solução está depois do ponto médio, logo isto equivale a encontrar o (P - val)-ésimo lugar (onde val é o número de lugares desocupados no intervalo de a ao ponto médio) no intervalo [ponto médio + 1, b]. Temos a resposta quando chegarmos a um intervalo onde a = b.

Notem que para o caso análogo é necessário mudar um bocadinho os intervalos, mas o método é o mesmo. Falta então a estrutura para resolver a RSQ.


Segment Trees:
Uma Segment Tree (ou "árvore de segmentos") é uma estrutura de dados que guarda um conjunto de dados em intervalos. A estrutura tem uma forma de árvore binária onde cada nó é uma metade do intervalo do seu pai. Assim, pode ser usada para retornar somas de intervalos de valores (como é necessário para o problema). Também permite alterar o valor de um certo elemento dinamicamente. Todas as operações têm complexidade O(lg(N)), que é evidentemente sublinear.


Fenwick Trees:
Uma Fenwick Tree (também chamada de Binary Indexed Tree) é uma estrutura de dados que permite guardar somas acumuladas de um conjunto de elementos (que corresponde ao problema do RSQ) e alterar valores de elementos, ao criar uma árvore que aproveita a forma binária (base 2) de cada número.


Buckets:
Talvez a alternativa mais simples de perceber, mas também a mais ineficiente e talvez mais difícil de implementar, são Buckets (ou "Baldes"). Este método corresponde a dividir o conjunto de elementos em vários conjuntos mais pequenos (chamados de "baldes"), normalmente de tamanho sqrt(N) onde se pre-calcula a sua soma. Assim, para saber a soma num intervalo basta percorrer apenas os baldes e nos limites (porque os extremos do intervalo ficam no meio de um balde cada um), pesquisar linearmente de cada extremo até ao primeiro balde. Alterar valores corresponde a alterar o valor do balde.


Solução Alternativa:
Era possível alterar o algoritmo descrito com pesquisa binária e aproveitar a estrutura da Segment Tree. A ideia era exatamente a mesma, mas utilizar a Segment Tree para dividir os intervalos. Assim, era possível inclusivé alterar logo o valor do lugar para ocupado (que é mais eficiente). Assim, esta solução, apesar de ter a mesma complexidade que a anterior, é ligeiramente mais eficiente em termos de implementação.

Em termos de complexidades finais, as soluções com árvores utilizam RSQ logaritmico (pesquisa e mudança de valores) e como a pesquisa binária também tem tempo logarítmico, a complexidade é de: O(Q*lg(N)^2). No caso de Buckets, como a pesquisa tem complexidade de raíz quadrada e a pesquisa binária mantém-se: O(Q*lg(N)*sqrt(N)).

Ligações interessantes: