Delta Encoding
Problemas de atualização em intervalos
Suponha que você se depare com um problema do seguinte tipo:
Você recebe um vetor v
, inicialmente com todos seus números zerados, e q
queries. cada query virá representada por 3 inteiros (l,r,x). Ao receber cada query, a mudança esperada é: “Para cada número no vetor, seja i
o seu índice. Se l <= i <= r
, v[i] deve ter x adicionado ao seu valor. Ao final do programa, deve-se imprimir o vetor todo com seus valores atualizados.
A primeira solução que já vem a cabeça é a seguinte:
int n,q;
scanf("%d %d",&n,&q);
vi v(n,0);
for (int i = 0; i < q; ++i){
int l,r,x;
scanf("%d %d %d",&l,&r,&x);
for (int j = l; j <= r; ++j){
v[j] += x;
}
}
Nessa solução, para cada uma das q
queries, o pior caso seria l = 0
e r = n-1
, que faz o laço interno iterar n
vezes, o que nos dá uma complexidade O(n*q)
. Precisamos ser mais rápidos.
Definição
Delta encoding é uma técnica para resolver problemas desse tipo. Precisamos fazer atualizações em intervalos, mas como o vetor só precisa ser visualizado no final, o que fazemos é fazer todas de uma vez só no final. A ideia por trás dessa solução funciona da seguinte forma: Vamos criar um vetor auxiliar delta
, que é o vetor que vai acumular as atualizações.
Atualização do intervalo (l,r)
Dessa forma, quando estivermos lendo uma query (l,r,x), faremos o seguinte:
vector<int> delta(n,0);
for(int i = 0; i < q; i++){
int l,r,x;
scanf("%d %d %d",&l,&r,&x);
delta[l] += x;
delta[r+1] -= x;
}
O trecho de código acima é o delta encoding por si só, o nome é esse porque estamos codificando a informação de que precisamos atualizar aquele intervalo. A parte interessante, é que nesse laço, nossa atualização funciona em O(1)
.
Obtendo os valores após várias atualizações
Tendo as informações organizadas dessa forma, isso nos permite percorrer o vetor delta
da esquerda pra direita obtendo os valores finais do vetor, em O(n)
.
Isso é feito criando uma variável atual
, inicializada com 0. Então, enquanto percorremos o vetor, na i-ésima iteração, sempre somamos a essa variável delta[i]
, após isso, a configuração final de v[i]
será o valor de atual
.
Então, é por esse motivo que guardamos a informação daquela forma no vetor. Como vamos percorrer da esquerda pra direita, quando fazemos v[l] += x
, estamos dizendo, quando você passar no índice l
, você deve adicionar mais x
a resposta. E quando fazemos v[r+1] -= x
, dizemos: quando seu índice for maior que r
, pare de considerar x no resultado.
Dessa forma, a versão final:
vector<int> delta(n+5,0);
vector<int> v(n);
for(int i = 0; i < q; i++){
int l,r,x;
scanf("%d %d %d",&l,&r,&x);
delta[l] += x;
delta[r+1] -= x;
}
int atual = 0;
for(int i = 0; i < n; i++){
atual += delta[i];
v[i] = atual;
}
for(int i = 0; i < n; i++){
printf("%d ",v[i]);
}
printf("\n");
Ressalvas
Análogo as somas prefixas, mas de forma inversa, essa ED processa atualizações em intervalo muito rapidamente O(1)
, porém, quando é necessário saber os valores do vetor, e há atualizações pendentes, é necessário percorrer novamente o vetor todo O(n)
, então essa ED funciona melhor com muitas atualizações e poucas consultas.