Escrito por Thiago Mota
Pense no seguinte problema, dado um vetor de elementos iremos fazer perguntas no vetor do tipo perguntando qual o menor valor do vetor no intervalo de até . Esse problema pode ser facilmente resolvido utilizando uma Segment Tree com a complexidade de para cada pergunta, mas nesse problema não temos update logo não é necessário o uso da Segment Tree, logo iremos resolver utilizando uma técnica de "programação dinâmica".
Iremos definir uma tabela de tal forma que seja o menor valor no intervalo que começa em e tem tamanho , ou seja, para o vetor teremos a seguinte tabela:
O tamanho dessa tabela será por volta de de memória, pois o segundo argumento só vai até o logaritmo do tamanho total.
Para computar essa tabela, iremos olhar paras os valores calculados anteriormente, olhe para a imagem:
Com isso, notamos que para calcular a podemos olhar o menor entre e , sendo o início do próximo intervalo, como o tamanho do intervalo é , o vai ser , logo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (n) é o tamanho do vetor | |
// (log_n) é o log na base 2 de n | |
// (v) é o vetor | |
void computa() { | |
// Caso inicial (tamanho 1, j = 0) | |
for (int i = 1; i <= n; i++) | |
tab[i][0] = v[i]; // Intervalo [i, i] | |
for (int j = 1; j <= log_n; j++) { | |
// Note que o for do j vem primeiro, isso é necessário pois | |
// o jeito que tab(i, j) é calculado necessita que todos os i's | |
// para j-1 estejam calculados. | |
for (int i = 1; i <= n; i++) { | |
// Checando se o intervalo que começa em i e tem tamanho 2^j existe. | |
// Para isso basta checar se o fim do intervalo é menor ou igual a n | |
if (i + (1<<j) - 1 > n) break; | |
tab[i][j] = min(tab[i][j-1], tab[i + (1<<(j-1))][j-1]); | |
} | |
} | |
} |
Agora para calcular o mínimo de qualquer intervalo iremos simplesmente calcular o sendo o tamanho do intervalo e pegar o mínimo dos intervalos que começam em e tem tamanho , ou seja, e outro que termina e e tem tamanho , ou seja, , tendo assim o mínimo do intervalo todo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (n) é o tamanho do vetor | |
// (v) é o vetor | |
int flog(int x) { // Calcula a parte inteira do log2 de x em O(1) ( para int ) | |
return 31 - __builtin_clz(x); | |
} | |
int flog(long long x) { // Calcula a parte inteira do log2 de x em O(1) ( para long long ) | |
return 63 - __builtin_clzll(x); | |
} | |
int query(int l, int r) { | |
int k = flog(r - l + 1); | |
return min(tab[l][k], tab[r - (1<<k) + 1][k]); | |
} |
Note que essa tabela só funciona pois o menor entre e é o próprio , pois existe sobreposição de intervalos na hora de fazer a query, ou seja, não funcionaria caso a pergunta fosse a soma do intervalo.
Essa ideia de montar uma tabela nas potências de dois tem o nome de Sparse Table e é muito utilizada na hora de calcular valores em uma árvore como no Menor Ancestral Comum, uma questão que usa uma ideia similar é a Crayfish Scrivener dá IOI 2012 caso você queira exercitar a ideia.