Escrito por Lúcio Cardoso
Conhecimento prévio necessário:
A Merge Sort Tree é uma extensão da Segment Tree, e por isso, é fortemente recomendado um bom entendimento desta estrutura antes de conferir esta Ideia.
Com a Merge Sort Tree, podemos responder diversos tipos de queries. Iremos demonstrar, em particular, a sua aplicação na seguinte query: Dado um vetor de tamanho (), indique quantos números maiores que existem em um segmento .
Descrição da Estrutura
A estrutura da Merge Sort Tree é bastante semelhante à da Segment Tree. No entanto, ao invés de guardarmos um valor inteiro no nó que representa o intervalo , guardaremos um vector ordenado de inteiros, contendo todos os números do intervalo de .
Perceba que, organizando a estrutura desta forma, em cada um dos níveis da Merge Sort Tree, a somatória do tamanho dos vectors utilizados será exatamente , pois contamos cada elemento de uma vez. Logo, a memória total da Merge Sort Tree é .
Para construir a estrutura, usaremos uma função build semelhante à da Segment Tree. No entanto, sempre que chegarmos em uma folha, iremos inserir no seu vector o número no índice que esta representa. Após isso, para construir um nó que não é folha, juntaremos os nós filhos da esquerda e da direita usando o comando do C++, que junta dois vectors e os ordena em um só (com complexidade ).
Segue o código da função build():
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
// Ideia Noic 11 - Merge Sort Tree | |
// Função build() | |
const int maxn = 1e5+10; | |
int num[maxn]; | |
// vector para cada nó da árvore | |
vector<int> tree[4*maxn]; | |
void build(int node, int l, int r) | |
{ | |
if (l == r) | |
{ | |
tree[node].push_back(num[l]); | |
return; | |
} | |
int mid = (l+r)>>1; | |
build(2*node, l, mid); build(2*node+1, mid+1, r); | |
int a = 2*node, b = 2*node+1; | |
// a função merge() junta dois vectors em um só, deixando o vector final ordenado | |
// para mais informações sobre esta funçao, confira a referência do C++ | |
merge(tree[a].begin(), tree[a].end(), tree[b].begin(), tree[b].end(), back_inserter(tree[node])); | |
} |
Respondendo a query
Imagine que recebemos a mesma operação indicada no começo em um intervalo . Podemos visitar cada um dos nós da MS Tree que representam esse intervalo (da mesma maneira que a função query() da segment tree) e calcular a resposta de maneira independente para cada um deles.
Agora, reduzimos o problema para calcular a quantidade de elementos maiores que que existem em um vector ordenado (representando um nó da MS Tree). Podemos calcular isto usando a função upper_bound() do C++ em , pois esta função retorna um iterator para primeira posição com valor maior que no vector.
Desta maneira, a complexidade necessária para responder esta query é , pois usamos uma operação em para nós.
Segue o código da função query():
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
// Ideia Noic 11 - Merge Sort Tree | |
// Função query() | |
// calcula a quantidade de valores > x no intervalo [a, b] do vetor | |
int query(int node, int l, int r, int a, int b, int x) | |
{ | |
// estamos fora do intervalo | |
if (l > b || r < a) return 0; | |
// tree[node].end() aponta após a ultima posição do vector | |
// logo, se subtrairmos deste ponteiro o primeiro iterador > x no vector, | |
// encontraremos a resposta para este nó | |
if (l >= a && r <= b) | |
return tree[node].end()-upper_bound(tree[node].begin(), tree[node].end(), x); | |
int mid = (l+r)>>1; | |
// fazemos a recursão para os filhos do nó | |
return (query(2*node, l, mid, a, b, x)+query(2*node+1, mid+1, r, a, b, x)); | |
} |
Outras aplicações
Perceba que, para este problema em específico, usamos um vector em cada nó da nossa árvore, com memória . Porém, para resolver outros tipos de problemas, podemos guardar outras estruturas nos nós da árvore, como por exemplo sets, maps, BITs ou até mesmo outra segment tree.
Contanto que o tamanho da estrutura dentro de um nó representando , seja , podemos usá-la com complexidade de memória . Logo, devemos tomar cuidado ao utilizar BITs ou segment trees dentro de um nó, pois estas não podem ter um tamanho fixo definido globalmente. Uma alternativa seria utilizar um vetor de vectors alocados dinamicamente para a BIT ou uma segment tree esparsa (utilizando ponteiros), por exemplo.