Aplicaciones extendidas de matrices de árboles.

O(N) logros #

La forma más básica de construir una matriz de árbol es agregar valor a cada punto.

Complejidad del tiempo : O (NlogN)

Código

int tr[N];	// tr[] 存储树状数组数据
int a[N];	// a[] 存储原数组数据
int n;		// 数列长度

int lowbit(int x) { return x & -x; }

void add(int x, c) {
    for (int i = x; i <= n; x += lowbit(x)) 
        tr[i] += c;
}

// 建树
void build() {
    for (int i = 1; i <= n; i++)
        add(i, a[i]);
}

No hay muchos escenarios de aplicación para la construcción de árboles O (N), porque la complejidad temporal de la construcción de árboles ordinarios es NlogN. Esta vez la complejidad es aceptable para la mayoría de las preguntas, a menos que algunas preguntas se bloqueen deliberadamente o algo así.

Método 1 #


Sabemos que para la matriz de árbol tr[x], el rango de intervalo que mantiene es [x−lowbit(x)+1,x], entonces tr[x]=a[x−lowbit(x)+1,x] . Entonces podemos encontrar primero la suma del prefijo de a[], y luego encontrar la suma del intervalo de [x−lowbit(x)+1,x] a través de la suma del prefijo O(1), logrando así O(N) para construir un conjunto de árboles.

Código

int tr[N];	// 树状数组数据
int a[N];	// 原数组数据
int sum[N];	// sum[] 存储 a[] 的前缀和
int n;		// 数列长度

int lowbit(int x) { return x & -x; }

// 建树
void build() {
    // 求 a[] 的前缀和 sum[]
	for (int i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + a[i];
    
    // 利用前缀和求出区间和,O(N)建树
   	for (int i = 1; i <= n; i++)
        tr[i] = sum[i] - sum[i - lowbit(i)];
}

Método 2 #


imagen

Al observar la figura anterior, encontramos que para la construcción del árbol O (logN), cuando se actualiza C [x], todos actualizarán sus nodos principales. Entonces esto hará que C[x] actualice su nodo principal varias veces, lo que generará muchos cálculos repetidos.

También sabemos que el nodo padre de C[x] es C[x+lowbit(x)] . Luego podemos pasar de 1 a n y dejar que cada nodo C[i] actualice su nodo principal solo una vez.

De esta manera, también puede lograr O (N) para construir una matriz de árbol. Además, este método es menos problemático que el método uno y no es necesario preprocesar la suma del prefijo por adelantado.

Código

int tr[N];	// 树状数组数据
int a[N];	// 原数组数据
int n;		// 数列长度

int lowbit(int x) { return x & -x; }

// 建树
void build() {
    for (int i = 1; i <= n; i++) {
        tr[i] += a[i];
        
        int fa = i + lowbit(i);	// 获得父节点下标
        if (fa <= n) 	// 判断父节点是否超出数列范围
            tr[fa] += tr[i];
    }
}

Intervalos de mantenimiento y #

Modificación de un solo punto, consulta de intervalo #


Dada una matriz de longitud n, realice las dos operaciones siguientes Q veces en la matriz:

  • 1 x y: Suma el número en la posición xey (o resta y, cámbialo a y y multiplícalo por y).
  • 2 x y: Consulta la suma del intervalo [x,y].

Este es el uso más básico de las matrices de árboles.

complejidad del tiempo

  • Modificación de punto único O (logN)
  • Consulta de intervalo O (logN)

Código

int tr[N];
int a[N];
int n;

int lowbit(int x) { return x & -x; }

// 给 x 位置的数加上 c
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}

// 查询 1 ~ x 的区间和
void query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        res += tr[i];
   	
    return res;
}

// 使用

add(x, c);	// 给 x 位置的数加上 c
add(x, y - (query(x) - query(x - 1)));	// 讲 x 位置的数改为 y

int val1 = query(x);	// 查询 [1, x] 的区间和
int val2 = query(r) - query(l - 1);	// 查询 [l, r] 的区间和
int val3 = query(x) - query(x - 1);	// 查询 x 位置的值

Modificación de intervalo, consulta de un solo punto #


Dada una matriz de longitud n, realice las dos operaciones siguientes Q veces en la matriz:

  • 1 x y k: Suma k a todos los números en el intervalo [x,y] (o resta k de todos los números).
  • 2 x: Consulta el valor de la posición x

Aquí necesitamos usar la diferencia para usar la matriz de árbol para mantener la matriz de diferencia .

  • Modificación de intervalo:add(l, k), add(r + 1, -k);
  • Consulta de un solo punto:query(y) - query(x - 1);

complejidad del tiempo

  • Modificación de intervalo: O (logN)
  • Consulta de punto único: O (logN)

Código

int tr[N];
int a[N];
int n;

int lowbit(int x) { return x & -x; }

// 给 x 位置的数加上 c
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

// 查询 1 ~ x 的区间和
void query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

// 使用

add(r, c), add(l - 1, c);	// 讲区间 [l, r] 都加上 c

int val = query(x) + a[x];	// 查询 x 位置的值

Modificación de intervalo, consulta de intervalo #


Dada una matriz de longitud n, realice las dos operaciones siguientes Q veces en la matriz:

  • 1 x y k: Suma k a todos los números en el intervalo [x,y] (o resta k de todos los números).
  • 2 x y: Consulta la suma del intervalo de [x,y].

Cuando nos encontramos con este tipo de problemas, normalmente optamos por utilizar un árbol de segmentos de línea para resolverlo, pero también se pueden implementar matrices de árboles.

Aquí primero pensamos en usar matrices de diferencias para implementarlo, pero ¿cómo podemos consultar la suma del intervalo?

Para la secuencia a[i], su matriz de diferencias es b[i]=a[i]−a[i−1], y el valor de a[i] es la suma del prefijo de b[i]. Entonces para la suma del prefijo de a[i] tenemos,

Entonces hay, entonces hay, (1)∑i=1xai=a1+a2+a3+...+ax(2)=b1(3)+b1+b2(4)+b1+b2+b3(5) +b1+b2+b3+b4(6)⋮(7)+b1+b2+b3+b4+⋯+bx(8) Entonces hay, ∑i=1xai=∑i=1x∑j=1ibj

Si complementamos la fórmula enumerada, se convierte en una matriz, como se muestra en la siguiente figura.

imagen

Si sumamos en función de las columnas, entonces la fórmula para la suma del prefijo se puede transformar en,

(9)∑i=1xai=(b1+b2+b3+...+bx)×(x+1)−(b1+2b2+3b3+...+xbx)(10)=∑i=1xbi−∑i =1xi×bi

De esta manera, podemos transformar el problema para mantener las matrices de suma de prefijos de bi e i×bi, y luego usar dos matrices de árbol tr1 y tr2 para mantener las sumas de prefijos de bi e i×bi respectivamente.

  • Consulta de intervalo: obtenga la suma del prefijo y calcúlela directamente según la fórmula.
    • Complejidad del tiempo: O (logN)
  • Modificación de intervalo: realice las modificaciones correspondientes a la suma del prefijo mantenida por tr1 y tr2 respectivamente.
    • Complejidad del tiempo: O (logN)
    • Para tr1, ejecute add(x, k), add(y + 1, -k);
    • Para tr2, ejecute add(x, x * k), add(y + 1, (y + 1) * k);

Código

#define int long long

int tr1[N];	// 维护 b[i] 的前缀和
int tr2[N];	// 维护 i * b[i] 的前缀和
int a[N];	// 原数组
int n;

int lowbit(int x) { return x & -x; }

// 对树状数组 tr[] 执行加和操作
void add(int tr[], int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

// 对树状数组 tr[] 执行查询前缀和的操作
int query(int tr[], int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

// 建树
void build() {
    for (int i = 1; i <= n; i++) {
        int b = a[i] - a[i - 1];	// 差分 b[i]
        add(tr1, i, b);
        add(tr2, i, i * b);
    }
}

// 查询数列的前缀和
int pre_sum(int x) {
    return query(tr1, x) * (x + 1) - query(tr2, x);
}

// 执行操作

// 建树(初始化)
build();

// 区间查询
int val = pre_sum(y) - pre_sum(x - 1);	// [x, y] 的区间和

// 区间修改
add(tr1, x, k), add(tr1, y + 1, -k);	// 修改 tr1[]
add(tr2, x, x * k), add(tr2, y + 1, (y + 1) * -k);	// 修改 tr2[]

Mantenimiento integrado del código de finalización de suma de intervalos, admite modificación de intervalos y consulta de intervalos (las funciones están bien encapsuladas)

Mantener la suma de la submatriz bidimensional (matriz de árbol bidimensional) #

Modificación de punto único, consulta de submatriz #


Dada una matriz A de n × m, realice las dos operaciones siguientes en la matriz Q veces:

  • 1 x y k: Suma k a los elementos Ax, y (o resta k de ambos).
  • 2 a b c d: Consulta la suma de todos los números en la submatriz cuya esquina superior izquierda es (a,b) y cuya esquina superior derecha es (c,d).

Una matriz de árbol bidimensional es una matriz de árbol dentro de una matriz de árbol. Sobre la base de la matriz de árbol unidimensional original, los nodos de esta matriz de árbol se utilizan para crear una matriz de árbol, logrando así la función de mantener la suma de la matriz.

Pensamos en la lógica de modificación de la matriz de árbol, es decir, cuando se modifica un determinado nodo, cuántos nodos se verán afectados y luego se modifican estos nodos afectados. Por lo tanto, los cambios en los nodos en la matriz A afectarán los valores de los nodos de la matriz de árbol unidimensional y luego realizarán las modificaciones correspondientes. De manera similar, los cambios en la matriz de árbol unidimensional también afectarán los valores de los nodos de la matriz de árbol bidimensional, y se deben realizar las modificaciones correspondientes.

La modificación de una matriz de árbol unidimensional es O (logN), por lo que afectará a los nodos logN. Para cada nodo modificado de la matriz de árbol unidimensional, se requiere O (logN) para actualizar el valor del nodo de la matriz de árbol bidimensional.

Entonces, la complejidad temporal de la operación de modificación es O (log2N).

En cuanto a la inicialización de la suma del prefijo bidimensional, existe  sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]; (no hay una explicación específica, si no la sabes, puedes aprenderla primero, lo mismo ocurre con lo siguiente).

De la misma manera, para la operación de consulta, sabemos que la fórmula para encontrar la submatriz mediante la suma de prefijos bidimensionales es Sum = sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];,.

Luego solo necesita obtener la suma del prefijo que mantienen y luego calcular el resultado de acuerdo con la fórmula, y la complejidad del tiempo también es O (log2N).

Entonces esta es la lógica básica de una matriz de árbol bidimensional, logrando así la función de mantener la suma de la matriz.

complejidad del tiempo

  • Inicialización: N2log2N

  • Modificación de un solo punto: O (log2N)

  • Consulta de submatriz: O (log2N)

Código

#define int long long

int tr[N][N];	// 二维树状数组
int a[N][N];	// 原数组
int n, m;	// 行高和列宽

int lowbit(int x) { return x & -x; }

// 给 (x, y) 位置的数加上 c
void add(int x, int y, int c) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= m; j += lowbit(j))
            tr[i][j] += c;
}

// 查询 (x, y) 位置的二维前缀和
int query(int x, int y) {
    int res = 0;
    
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            res += tr[i][j];
    
    return res;
}

// 建立二维树状数组(初始化)
void build() {
	for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            int val = query(i - 1, j) 
                + query(i, j - 1) 
                - query(i - 1, j - 1) 
                + a[i][j];
            add(i, j, val);
        }
    }
}

// // 查询左上角为(x1, y1), 右下角为(x2, y2) 的子矩阵的和
int query(int x1, int y1, int x2, int y2) {
    return query(x2, y2) 
        - query(x1 - 1, y2) 
        - query(x2, y1 - 1) 
        + query(x1 - 1, y1 - 1);
}

// 使用

build();	// 初始化

add(x, y, c);	// 给 (x, y) 位置的数加上 c
add(x, y, -c);	// 给 (x, y) 位置的数减去 c

int sum1 = query(x, y);		// 查询左上角为(1, 1), 右下角为(x, y) 的子矩阵的和
int sum2 = query(a, b, c, d);	// 查询左上角为(a, b), 右下角为(c, d) 的子矩阵的和

Modificación de submatriz, consulta de punto único #


Dada una matriz A de n × m, realice las dos operaciones siguientes en la matriz Q veces:

  • 1 a b c d k: Sume k a cada elemento de la submatriz con (a,b) en la esquina superior izquierda y (c,d) en la esquina superior derecha (o reste k de ambos).
  • 2 x y: Preguntar por el valor del elemento Ax,y.

Es lo mismo que la modificación de intervalo y la consulta de un solo punto anteriores: esta utiliza una matriz de árbol unidimensional para mantener una matriz de diferencias unidimensional. De la misma manera, también podemos usar una matriz de árbol bidimensional para mantener una matriz de diferencias bidimensional.

Para la matriz de diferencias bidimensional, cada una de nuestras operaciones de modificación de matriz es, b[x1][y1] += c, b[x2 + 1, y1] -= c, b[x1, y2 + 1] -= c, b[x2 + 1][y2 + 1] += c; y cada operación de consulta de un solo punto es encontrar una suma de prefijo bidimensional.

complejidad del tiempo

  • Modificación de submatriz: O (log2N)
  • Consulta de punto único: O (log2N)

Código

#define int long long

int tr[N][N];	// 二维树状数组
int a[N][N];	// 原数组
int n, m;	// 行高和列宽

int lowbit(int x) { return x & -x; }

void add(int x, int y, int c) {
    for (int i = x; i <= n; i += lowbit(i))
        for (int j = y; j <= m; j += lowbit(j))
            tr[i][j] += c;
}

void query(int x, int y) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
        for (int j = y; j; j -= lowbit(j))
            res += tr[i][j];
    return res;
}

// 将左上角为 (x1, y1), 右下角为 (x2, y2) 的子矩阵的每个元素都加上 c
void add(int x1, int y1, int x2, int y2, int c) {
    add(x1, y1, c);
    add(x2 + 1, y1, -c);
    add(x1, y2 + 1, -c);
    add(x2 + 1, y2 + 1, c);
}

// 使用
add(x1, y1, x2, y2, c);	// 将左上角为 (x1, y1), 右下角为 (x2, y2) 的子矩阵的每个元素都加上 c

int val = query(x, y) + a[x][y];	// 查询 (x, y) 位置的元素值

Modificación de submatriz, consulta de submatriz #


Dada una matriz A de n × m, realice las dos operaciones siguientes en la matriz Q veces:

  • 1 a b c d k: Sume k a cada elemento de la submatriz con (a,b) en la esquina superior izquierda y (c,d) en la esquina superior derecha (o reste k de ambos).
  • 2 a b c d: Consulta la suma de todos los números en la submatriz cuya esquina superior izquierda es (a,b) y cuya esquina superior derecha es (c,d).

Podemos pensar como lo hicimos con la suma de intervalo unidimensional anterior y resolver el problema manteniendo una matriz bidimensional de sumas de prefijos.

No entraré en detalles sobre ideas específicas y el proceso de derivación. Si desea saber más, puede leer este blog: Notas de estudio de estructura de datos: matriz de árbol bidimensional - Zhihu

La idea específica es utilizar cuatro matrices de árboles bidimensionales para mantener di,j, (i−1)di,j, (j−1)di,j, (i−1)(j−1)di,j respectivamente. Matrices de suma de prefijos bidimensionales.

Luego, la suma del prefijo se calcula utilizando la fórmula derivada,

sn,m=nm∑i=1n∑j=1mdi,j−m∑i=1n∑j=1m(i−1)di,j−n∑i=1n∑j=1m(j−1)di, j+∑i=1n∑j=1m(i−1)(j−1)di,j

Código

#define int long long

int a[N][N], b[N][N], c[N][N], d[N][N];	// 二维树状数组
int n, m;

int lowbit(int x) { return x & -x; }

void add(int x, int y, int v) {
	for (int i = x; i <= n; i += lowbit(i)) {
        for (int j = y; j <= m; j += lowbit(j)) {
            a[i][j] += v;
            b[i][j] += (x - 1) * v;
            c[i][j] += (y - 1) * v;
            d[i][j] += (x - 1) * (y - 1) * v;
        }
    }
}

int query(int x, int y) {
	int res = 0;
    for (int i = x; i; i -= lowbit(i)) {
        for (int j = y; j; j -= lowbit(j)) {
            res += x * y * a[i][j]
                - y * b[i][j]
                - x * c[i][j]
                + d[i][j];
        }
    }
    return res;
}

// 将左上角为 (x1, y1), 右上角 (x2, y2) 的子矩阵的所有元素加上 c
void add(int x1, int y1, int x2, int y2, int c) {
    add(x1, y1, v);
    add(x1, y2 + 1, -v);
    add(x2 + 1, y1, -v);
    add(x2 + 1, y2 + 1, v);
}

// 查询左上角为 (x1, y1), 右上角 (x2, y2) 的子矩阵的元素和
int query(int x1, int y1, int x2, int y) {
    return query(x2, y2) 
        - query(x1 - 1, y2)
        - query(x2, y1 - 1)
        - query(x1 - 1, y1 - 1);
}

// 使用

add(x1, y1, x2, y2, c);	// 将左上角为 (x1, y1), 右上角 (x2, y2) 的子矩阵的所有元素加上 c

int sum = query(x1, y1, x2, y2);// 查询左上角为 (x1, y1), 右上角 (x2, y2) 的子矩阵的元素和

Encuentra el número de pares en orden inverso #


Dada una matriz de longitud n, encuentre el número de pares en orden inverso.

Par inverso : para 1≤i<j≤n, hay ai>aj.

La ordenación por combinación puede encontrar el número de pares de orden inverso en una secuencia y la complejidad temporal es O (logN). Las matrices de árboles también pueden resolver este tipo de problemas, la complejidad del tiempo también es O (logN) y la complejidad del espacio es menor que la clasificación por fusión.

Para resolver los números en orden inverso, la matriz de árbol se obtiene encontrando el número de números en el lado izquierdo de cada ai que es mayor que él y luego sumándolos todos. Si definitivamente es imposible verificar cada vez que se atraviesa, ¿cómo se puede encontrar la cantidad de números en el lado izquierdo de cada ai que es mayor que él?

Del 1 al n, utilice ai como elemento de subíndice y +1 el número en la posición ai. Luego consultamos la suma del intervalo de 1∼ai cada vez, y el valor obtenido es el número de elementos en 1∼i que son menores o iguales que ai (incluido el propio ai). Entonces el número de elementos en 1∼i que es mayor que ai es i−sum[1,ai].

imagen

 

imagen

 

imagen

 

imagen

 

imagen

 

imagen

 

imagen

De esta manera, atravesamos 1∼n y realizamos una consulta de suma de prefijo y una modificación de punto único en O (logN) cada vez, por lo que la complejidad del tiempo total es O (NlogN).

Además, este enfoque consiste en calcular ai como un subíndice. Para los casos en los que ai es un número negativo o un número muy grande , es necesario agregar operaciones de discretización .

Si este es el caso, el consumo de tiempo y espacio de la matriz de árbol será mayor que el de la ordenación por fusión (aunque la complejidad total de tiempo y espacio es la misma). De hecho, esto refleja los beneficios de la clasificación por combinación para encontrar pares de orden inverso. No es necesario considerar el rango de valores de ai. Solo se puede decir que cada uno tiene sus propias ventajas y desventajas.

Código

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;

int tr[N];
int a[N];
int n;

int lowbit(int x) { return x & -x; }

void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main() {
    cin >> n;
    
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    LL res = 0;
    
    for (int i = 1; i <= n; i++) {
        add(a[i], 1);
        // 求逆序对的个数
        res += i - query(a[i]);
    }
    
    cout << res << "\n";
    
    return 0;
}

Código que requiere operaciones de discretización.

Encuentra el número de elementos menores que x en la secuencia #


De acuerdo con la idea anterior de encontrar pares de orden inverso, podemos encontrar el número de elementos en la secuencia que son menores que (mayor que, menor o igual que, mayor o igual que)  x.

De manera similar, si hay números negativos en la secuencia o los números son muy grandes, se necesitará O (NlogN) para la discretización.

Tenga en cuenta aquí que este método solo admite consultas fuera de línea , la complejidad temporal del preprocesamiento es O (NlogN) y la complejidad temporal de cada consulta es O (logN).

Código

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int tr[N];
int a[N];
int n;

int lowbit(int x) { return x & -x; }

void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main() {
 	cin >> n;
    
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    // 预处理
    for (int i = 1; i <= n; i++)
        add(a[i], 1);
    
    // 查询
    int x;
    cin >> x;
    
    int num1 = query(x - 1);	// 查询小于 x 的元素个数
    int num2 = query(x);		// 查询小于等于 x 的元素个数
    
    int num3 = n - query(x);	// 查询大于 x 的元素个数
    int num4 = n - query(x - 1);// 查询大于等于 x 的元素个数
    
    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/2301_78834737/article/details/132004600
Recomendado
Clasificación