Definição de árvore de segmento de linha
Segmento de linha = matriz unidimensional
Como mostrado na figura: de acordo com o índice, uma matriz unidimensional é dividida em duas metades, cada nó da árvore do segmento de linha representa um subintervalo na matriz original e o nó da folha representa um comprimento de 1. Intervalo
O que uma árvore de linhas pode fazer
Principalmente para resolver o problema do intervalo, como consultar o valor máximo de um intervalo, adicionar ou subtrair um número a todos os elementos de um intervalo ou encontrar a soma de um determinado intervalo.Para intervalos estáticos, temos métodos melhores, como [ prefix Soma e matriz de diferenças ], [ ST do RMQ query static ]
Mas esses métodos têm desvantagens: e se nossa matriz for atualizada dinamicamente? Ou seja, consulta durante a atualização.
- árvore segmento pode atualizar dinamicamente o intervalo, enquanto consulta mais rápida
refere-se a O (log (n))
Estrutura de árvore de linha
Com base em muitos recursos acima, os nós de uma árvore de segmentos de linha precisam ter:
- O que está atualmente representado
- Filhos esquerdo e direito
- O valor armazenado (como o valor máximo do intervalo atualmente representado)
Se você usar uma matriz (ou seja, uma árvore binária completa) para armazenar, poderá ser necessário adicionar
- Subscrito na matriz
Você pode escrever rapidamente a definição da estrutura
typedef struct node
{
int l, r, i, val;
}node;
Na verdade, ele não precisa ser tão problemático, basta salvar [val], ou seja, o valor armazenado no nó da árvore de linhas pode ser armazenado em uma matriz unidimensional
Usamos uma matriz para simular uma árvore binária completa para armazenar nós, mas devido à natureza bipartida, podemos enviar diretamente o sub-intervalo representado pelo nó atual, seja ele pesquisando ou atualizando , para que o armazenamento precise armazenar apenas o valor do nó Ponto, porque se estiver cheio, o número real de nós é 1 + 2 + 4 + 8 + ... + n/2 + n
, ou seja , árvore binária completa
int tree[maxn*4];
Criação de árvore de segmentos
Use uma matriz para salvar os nós. Se você estiver familiarizado com a árvore binária, será rápido
. O índice do nó atual na matriz é i
, então o índice filho esquerdo 2*i
e o direito filho2*i+1
Também criado de forma recursiva, a matriz original com conhecida a[]
conservação, trees[]
uma matriz de valores de nó, armazenados a faixa de valor do nó que é o representante do maior elemento de
Definir os intervalos atuais estão a ser criado [l, r]
, array árvore i
subscrito representa esse intervalo
- Se l = r,
tree[i] = a[l]
encerre a recursão - Caso contrário, recursione as subárvores esquerda e direita, ou seja, crie duas metades de subintervalos e seus subscritos na árvore :,
2*i 与 2*i+1
atualize o subscrito i após o término da recursão, ou seja,tree[i] = max(tree[2*i], tree[2*i+1])
/*
i : 当前线段树节点在tree数组中的下标
l : 当前线段树节点代表的区间的左端点
r : 当前线段树节点代表的区间的右端点
*/
void creat(int i, int l, int r)
{
if(l==r) {tree[i]=a[l]; return;}
creat(2*i, l, (l+r)/2), creat(2*i+1, (l+r)/2+1, r);
tree[i] = max(tree[2*i], tree[2*i+1]);
}
Consulta de segmento de linha
Um intervalo de consulta, porque temos alguns valores subdivisão dois intervalos (ou seja, a presença de árvores na matriz), enquanto para encontrar um determinado intervalo, de modo a que estas secções são ligados entre si para olhar para o intervalo igual para [] , e, em seguida, mesclar as respostas a estes intervalos podem ser , Ainda use a consulta recursiva, e sempre podemos encontrar alguns intervalos adequados, porque o intervalo mínimo é o comprimento de 1; se você não conseguir, pode apenas destacá-lo.
Suponha que o intervalo de consulta seja Q. Só precisamos encontrar todos os subintervalos de Q e depois mesclar as respostas.Este processo usa recursão.
- Se o intervalo representado pelo nó recursivo atual não se cruzar com Q , pare a recursão e retorne o valor mínimo
- Se o intervalo representado pelo nó recursivo atual for um sub-intervalo de Q , pare a recursão e retorne o valor armazenado no intervalo atual
- Caso contrário, o intervalo representado pelo nó recursivo atual é dividido pela metade e os subintervalos esquerdo e direito são recursivos
/*
ql : 要查询的区间左端点
qr :要查询的区间右端点
i : 当前线段树节点在tree数组中的下标
l : 当前线段树节点代表的区间左端点
r : 当前线段树节点代表的区间右端点
*/
int query(int ql, int qr, int i, int l, int r)
{
if(qr<l || r<ql) return INT_MIN;
if(ql<=l && r<=qr) return tree[i];
return max(query(ql, qr, 2*i, l, (l+r)/2),
query(ql, qr, 2*i+1, (l+r)/2+1, r));
}
Atualização do intervalo da árvore do segmento de linha
A atualização de todos os números em um intervalo , a complexidade simples nlog (n), é um pouco lenta.Introduzimos um mecanismo de cache que armazena em cache todos os números no intervalo atual.Quando consultamos, podemos usar esse número Para fazer adição e subtração
int buffer[maxn*4];
Mas o problema é que, se você executar várias operações de atualização, e esses intervalos de atualização forem diferentes, e mesmo houver um cruzamento, a situação se tornará problemática
Para atualizar um intervalo Q, se se sabe que o número v a ser atualizado nesse intervalo é armazenado em cache, o resultado do valor máximo desse intervalo será adicionado a v, e nós modificaremos diretamente o valor armazenado na matriz em árvore, ou seja tree[i]+=v
, para que possamos imediatamente Atualize para o valor máximo desse intervalo e jogue o problema para os dois subintervalos da próxima camada , ou seja, o cache dos subintervalos esquerdo e direito foi aumentado!
A vantagem disso é que precisamos de um pequeno número de operações para concluir a modificação de um intervalo, e o restante das operações são deixadas para a consulta para "fazê-lo"
Vale ressaltar que
- Ao atualizar, somente quando o intervalo atual é um subintervalo de Q , o valor a ser adicionado pode ser armazenado em cache e, em seguida, a pergunta é lançada no subintervalo da próxima camada, e a resposta dessa camada é atualizada e retornada.
- Se você precisar recuar para a próxima camada ao atualizar, não se esqueça de lançar o cache dessa camada primeiro e depois operar
- Após a atualização recursiva da chamada, lembre-se de atualizar o valor dessa camada antes de retornar
- Antes de consultar, lembre-se sempre de limpar o cache do intervalo atual, ou seja, jogue o problema para a próxima camada
/*
清空在tree数组中i下标代表的区间的缓存,向下一层抛出缓存
*/
void clear_buf(int i)
{
// 更新当前节点存储的值
tree[i] += buffer[i];
// 将缓存传递到下一层的节点
buffer[2*i] += buffer[i];
buffer[2*i+1] += buffer[i];
// 清空当前节点缓存
buffer[i] = 0;
}
/*
ul : 要更新的区间左端点
ur : 要更新的区间右端点
i : 当前区间的值存储在tree数组i下标
l : 当前区间左端点
r : 当前区间右端点
val : 要更新的值
*/
void update(int ul, int ur, int i, int l, int r, int val)
{
if(ur<l || r<ul) return;
if(ul<=l && r<=ur)
{
buffer[i] += val;
clear_buf(i);
return;
}
clear_buf(i);
update(ul, ur, 2*i, l, (l+r)/2, val);
update(ul, ur, 2*i+1, (l+r)/2+1, r, val);
tree[i] = max(tree[2*i], tree[2*i+1]);
}
Ao mesmo tempo, a função de consulta também deve se lembrar de descartar o cache
int query(int ql, int qr, int i, int l, int r)
{
if(qr<l || r<ql) return INT_MIN;
clear_buf(i);
if(ql<=l && r<=qr) return tree[i];
return max(query(ql, qr, 2*i, l, (l+r)/2),
query(ql, qr, 2*i+1, (l+r)/2+1, r));
}
Código
#include <bits/stdc++.h>
using namespace std;
#define maxn 1000
int tree[maxn*4];
int buffer[maxn*4];
int a[maxn], n;
void creat(int i, int l, int r)
{
if(l==r) {tree[i]=a[l]; return;}
creat(2*i, l, (l+r)/2), creat(2*i+1, (l+r)/2+1, r);
tree[i] = max(tree[2*i], tree[2*i+1]);
}
void clear_buf(int i)
{
// 更新当前节点存储的值
tree[i] += buffer[i];
// 将缓存传递到下一层的节点
buffer[2*i] += buffer[i];
buffer[2*i+1] += buffer[i];
// 清空当前节点缓存
buffer[i] = 0;
}
int query(int ql, int qr, int i, int l, int r)
{
if(qr<l || r<ql) return INT_MIN;
clear_buf(i);
if(ql<=l && r<=qr) return tree[i];
return max(query(ql, qr, 2*i, l, (l+r)/2),
query(ql, qr, 2*i+1, (l+r)/2+1, r));
}
void update(int ul, int ur, int i, int l, int r, int val)
{
if(ur<l || r<ul) return;
if(ul<=l && r<=ur)
{
buffer[i] += val;
clear_buf(i);
return;
}
clear_buf(i);
update(ul, ur, 2*i, l, (l+r)/2, val);
update(ul, ur, 2*i+1, (l+r)/2+1, r, val);
tree[i] = max(tree[2*i], tree[2*i+1]);
}
int main()
{
memset(buffer, 0, sizeof(buffer));
cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
creat(1, 1, n);
update(1, 4, 1, 1, n, 10);
update(3, 4, 1, 1, n, 10);
for(int l=1; l<=n; l++)
for(int r=l; r<=n; r++)
cout<<l<<" "<<r<<" "<<query(l, r, 1, 1, n)<<endl;
return 0;
}
/*
8
1 2 3 4 5 6 7 8
*/
8
1 2 3 4 5 6 7 8
[1, 1] 11
[1, 2] 12
[1, 3] 23
[1, 4] 24
[1, 5] 24
[1, 6] 24
[1, 7] 24
[1, 8] 24
[2, 2] 12
[2, 3] 23
[2, 4] 24
[2, 5] 24
[2, 6] 24
[2, 7] 24
[2, 8] 24
[3, 3] 23
[3, 4] 24
[3, 5] 24
[3, 6] 24
[3, 7] 24
[3, 8] 24
[4, 4] 24
[4, 5] 24
[4, 6] 24
[4, 7] 24
[4, 8] 24
[5, 5] 5
[5, 6] 6
[5, 7] 7
[5, 8] 8
[6, 6] 6
[6, 7] 7
[6, 8] 8
[7, 7] 7
[7, 8] 8
[8, 8] 8