Définition de l'arborescence des segments de ligne
Segment de ligne = tableau unidimensionnel
Comme le montre la figure: selon l'indice, un tableau unidimensionnel est divisé en deux moitiés, chaque nœud d'arbre de segment de ligne représente une sous-plage dans le tableau d'origine et le nœud feuille représente une longueur de Intervalle
Que peut faire un arbre linéaire
Principalement pour résoudre le problème d'intervalle, comme interroger la valeur maximale d'un intervalle, ajouter ou soustraire un nombre à tous les éléments d'un intervalle, ou trouver la somme d'un certain intervalle. Pour les intervalles statiques, nous avons de meilleures méthodes telles que [ préfixe Tableau somme et différence ], [ ST fait une requête statique RMQ ]
Mais ces méthodes ont des inconvénients: que faire si notre tableau est mis à jour dynamiquement? Autrement dit, interrogez lors de la mise à jour.
- arbre de segment peut mettre à jour dynamiquement l' intervalle, alors que la requête plus rapide
se réfère à O (log (n))
Structure de l'arborescence des lignes
Sur la base de nombreuses fonctionnalités ci-dessus, les nœuds d'une arborescence de segments de ligne doivent avoir:
- Ce qui est actuellement représenté
- Enfants gauche et droit
- La valeur stockée (telle que la valeur maximale de l'intervalle actuellement représenté)
Si vous utilisez un tableau (c'est-à-dire un arbre binaire complet) pour stocker, vous devrez peut-être ajouter
- Indice dans le tableau
Vous pouvez rapidement écrire la définition de la structure
typedef struct node
{
int l, r, i, val;
}node;
En fait, cela n'a pas besoin d'être si gênant, enregistrez simplement [val], c'est-à-dire que la valeur stockée dans le nœud d'arbre de ligne peut être stockée dans un tableau à une dimension
Nous utilisons un tableau pour simuler un arbre binaire complet pour stocker les nœuds, mais en raison de la nature bipartite, nous pouvons directement extraire la sous-plage représentée par le nœud actuel, qu'il recherche ou soit mis à jour , donc le stockage n'a besoin que de stocker la valeur du nœud. Point, car s'il est plein, le nombre réel de nœuds est 1 + 2 + 4 + 8 + ... + n/2 + n
, c'est-à- dire , un arbre binaire complet
int tree[maxn*4];
Création de l'arborescence des segments
Utilisez un tableau pour enregistrer les nœuds. Si vous êtes familier avec l'arborescence binaire, vous serez rapide
. L'indice du nœud actuel dans le tableau est i
, puis l'indice enfant gauche 2*i
et l'indice enfant droit2*i+1
Également créé de manière récursive, le réseau d' origine avec connu a[]
conservation, trees[]
un tableau de valeurs stockées noeud, la plage de valeurs du noeud qui est le représentant le plus grand élément de
Définissez les intervalles actuels doivent être créés [l, r]
, un tableau d'arbres i
SUBSCRIPT représentent cette gamme
- Si l = r, alors
tree[i] = a[l]
terminer la récursivité - Sinon, récapitulez les sous-arbres gauche et droit, c'est-à-dire créez deux moitiés de sous-intervalles et leurs indices dans l'arborescence:,
2*i 与 2*i+1
mettez à jour l'indice i après la fin de la récursivité, c'est-à-diretree[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]);
}
Requête de segment de ligne
Un intervalle d'interrogation, parce que nous détenons certaines subdivisions valeurs deux intervalles (c. -à- la présence d'arbres dans le tableau), aussi longtemps que de trouver une certaine gamme, de sorte que ces sections sont reliées entre elles à la recherche de l'intervalle égal à [] , puis fusionner les réponses à ces intervalles peuvent être , Utilisez toujours une requête récursive, et nous pouvons toujours trouver quelques intervalles appropriés, car l'intervalle minimum est la longueur de 1, si vous ne pouvez pas le faire, vous pouvez simplement le tenir.
Supposons que l'intervalle de requête soit Q. Il nous suffit de trouver tous les sous-intervalles de Q, puis de fusionner les réponses. Ce processus utilise la récursivité.
- Si l'intervalle représenté par le nœud récursif actuel ne coupe pas Q , arrêtez la récursivité et retournez la valeur minimale
- Si l'intervalle représenté par le nœud récursif en cours est un sous-intervalle de Q , alors arrêtez la récursivité et renvoyez la valeur stockée dans l'intervalle en cours
- Sinon, l'intervalle représenté par le nœud récursif actuel est divisé en deux, et les sous-intervalles gauche et droit sont récursifs
/*
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));
}
Mise à jour de l'intervalle de l'arborescence des segments de ligne
La mise à jour de tous les nombres dans un intervalle , la simple complexité nlog (n), est un peu lente. Nous avons introduit un mécanisme de mise en cache qui met en cache tous les nombres dans l'intervalle en cours . Lorsque nous interrogeons, nous pouvons utiliser ce nombre Pour faire l'addition et la soustraction
int buffer[maxn*4];
Mais le problème est que si vous effectuez plusieurs opérations de mise à jour et que ces intervalles de mise à jour sont différents, et même s'il y a une intersection, la situation devient gênante
Pour mettre à jour un intervalle Q, si l'on sait que le nombre v à mettre à jour dans cet intervalle est mis en cache, alors le résultat de la valeur maximale de cet intervalle sera ajouté à v. Nous modifions directement la valeur stockée dans l'arborescence, c'est-à-dire que tree[i]+=v
nous pouvons immédiatement Mettez à jour à la valeur maximale de cet intervalle, puis renvoyez le problème aux deux sous-intervalles de la couche suivante , c'est-à-dire que le cache des sous-intervalles gauche et droit a été augmenté!
L'avantage de ceci est que nous avons besoin d'un petit nombre d'opérations pour terminer la modification d'un intervalle, et le reste des opérations est laissé à la requête pour "le faire"
Il convient de mentionner que
- Lors de la mise à jour, la valeur à augmenter ne peut être mise en cache que lorsque l' intervalle en cours est un sous-intervalle de Q , puis la question est renvoyée au sous-intervalle de la couche suivante, et la réponse de cette couche est mise à jour en même temps, puis renvoyée
- Si vous devez passer à la couche suivante lors de la mise à jour, n'oubliez pas de lancer d'abord le cache de cette couche, puis de faire fonctionner
- Après une mise à jour d'appel récursive, n'oubliez pas de mettre à jour la valeur de cette couche avant de revenir
- Avant d'interroger, n'oubliez pas de vider le cache de l'intervalle actuel, c'est-à-dire de jeter le problème à la couche suivante
/*
清空在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]);
}
Dans le même temps, la fonction de requête doit également se rappeler de jeter le 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));
}
Code
#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