Escrito por Sofhia de Souza
As técnicas de busca binária na BIT (ou Binary Indexed Tree) e busca binária na SegTree (ou Segment Tree) são bastante úteis, uma vez que conseguem reduzir operações de complexidade para . Para esse Noic Ideias, são necessário conhecimentos prévios de BIT e Segment Tree.
Busca Binária na BIT
Iremos iniciar com o seguinte problema:
Dado um vetor , com valores inteiros positivos. Nós queremos realizar três tipos de operação:
1. Atualizar o valor de determinada posição.
2. Computar a soma de prefixos da posição , sendo .
3. Procurar por certa soma de prefixos no vetor.
É possível notar que podemos resolver o problema com uma BIT normal, onde as operações terão as seguintes complexidades:
1.
2.
3.
Sendo as operações 1 e 2 operações normais de update e query na BIT, e a operação 3 uma busca binária, onde chamamos no máximo queries para encontrar a soma de prefixos desejada. No entanto, dependendo do tempo limite da questão, a complexidade da operação 3 pode ser muito lenta. Para resolvermos esse problema, usaremos da técnica de Busca Binária na BIT:
Faremos uma espécie de binary lifting na BIT. No binary lifting, nós adicionamos na nossa posição (ou "pulamos") em potências de 2, sempre da mais alta possível () até a mais baixa possível (), sem exceder o valor que queremos encontrar.
Vamos para um exemplo prático. Queremos encontrar a soma de prefixos , que está na posição da BIT. Para isso, iremos iniciar nossa busca com e , sendo o valor da minha soma de prefixos, e iremos incrementar essa posição em potências de 2, sempre começando da mais alta até a mais baixa, tendo certeza de que não estamos excedendo o valor de . Ou seja, sempre que incrementamos, nós adicionamos à a potência atual, e depois adicionamos à o valor existente na (só faremos isso caso ). No fim, chegaremos exatamente na posição desejada. Segue a implementação para melhor entendimento:
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
int bit[N]; | |
int busca_bit(int v) | |
{ | |
int sum = 0; | |
int pos = 0; | |
for(int i = 30 ; i >= 0 ; i--) | |
{ | |
if(pos + (1 << i) < N && sum + bit[pos + (1 << i)] <= v) | |
{ | |
sum += bit[pos + (1 << i)]; | |
pos += (1 << i); | |
} | |
} | |
return pos; | |
} |
Busca Binária na Segment Tree
Temos o seguinte problema:
Dado um vetor de tamanho e uma série de operações do tipo:
1. Atualizar a posição com o valor
2. Imprimir o -ésimo menor valor do vetor
Vemos que podemos resolver esse problema utilizando uma segtree de frequência normalmente, onde pra cada operação do tipo 1, fazemos normalmente o update em , e pra cada operação do tipo 2, fazemos uma busca binária nos valores, e para cada um fazemos uma query para descobrir se esse valor corresponde ou não ao -ésimo menor, dando uma complexidade de . Dependendo do tempo limite do problema, essa complexidade da operação do tipo 2 pode ser um pouco lenta. Para resolver isso, usaremos a técnica que chamamos de Busca Binária na Segtree.
Suponha que, para cada nó da segtree, você saiba o número de valores presentes nesse determinado intervalo [, ]. Você deseja encontrar o -ésimo menor valor para esse intervalo. Se a contagem de elementos em seu filho esquerdo for pelo menos , o -ésimo menor elemento no intervalo [, ] estará obrigatoriamente no filho esquerdo. Senão, ele estará no filho direito e será o ()-ésimo menor valor do intervalo do filho direito, em que é o número de valores no intervalo correspondente ao filho esquerdo.
A ideia da Busca Binária na Segtree acaba sendo mais intuitiva do que a na BIT, e é bastante útil em vários tipos de problemas. Segue a implementação para melhor entendimento:
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
int st[4*maxn]; //minha segtree de frequencia | |
int busca_seg(int p, int l, int r, int k) | |
{ | |
if(l == r) return l; //se l e r são iguais, cheguei no valor que eu queria | |
int meio = (l+r)/2; | |
if(st[p*2] >= k) //se meu filho da esquerda tiver pelo menos k valores, o k-esimo esta nele | |
{ | |
return busca_seg(p*2, l, meio, k); | |
} | |
else //senao, o k-esimo esta no meu filho da direita | |
{ | |
return busca_seg(p*2+1, meio+1, r, k-st[p*2]); | |
} | |
} |
Ambas as técnicas são bastante utilizadas e aplicadas de várias formas diferentes. Um problema bastante interessante que engloba as duas técnicas é o Fila, da OBI 2015. Recomendamos que tentem pensar e resolver o problema, e caso não consigam, vejam a solução no Comentário Noic da OBi 2015.