高级数据结构:树状数组

文章目录

树状数组

概念:

树状数组是一种可以快速区间修改和元素查询的结构,是线段树的低配版。原理是利用lowbit分解,大致感觉和二进制分解比较像,时间同样是l o g loglog级别的 。其实英文和它的作用更加贴切,就是通过二进制的索引产生像树一样的结构。

算法解释

树状数组的本质是用一个 "压缩版" 的树结构来存储数组的前缀和,核心是**lowbit **

关于lowbit

lowbit (x) 的定义:返回 x 的二进制表示中,最低位的 1 所对应的值。

比如:

x=5(二进制 101),最低位 1 在第 0 位(从 0 开始计数),对应值为 1,故 lowbit (5)=1;

x=6(二进制 110),最低位 1 在第 1 位,对应值为 2,故 lowbit (6)=2;

x=8(二进制 1000),最低位 1 在第 3 位,对应值为 8,故 lowbit (8)=8。

计算方式:负数在计算机是补码,x & -x就能提取最低位 1,是整个树状数组的根基。。

基础模板:单点修改,区间求和

P3374 【模板】树状数组 1 - 洛谷

规则

操作1:单点增加

操作2:求l,r区间和

核心真相:tree[]数组≠原数组a[]!!! tree[x存的是一段区间的和,不是ax。奇数位置是原数组的值,偶数位置是区间和

举例子 n=5

复制代码
a[1]=1  a[2]=5  a[3]=4  a[4]=2  a[5]=3

画图解释

复制代码
//这个操作得到tree
for(int i = 1; i <= n; i++)
    {
        int val;
        cin >> val;
        update(i, val);
    }

单点增加

复制代码
//单点增加:i位置加上delta
void update(int i, int delta)
{
    while(i <= n)
    {
        tree[i] += delta;
        i += lowbit(i);
    }
}

初始化a1\~n,得到偶数下标的数treei是区间和,奇数下标treei=ai

复制代码
update(1,1);   //a[1]=1
update(2,5);   //a[2]=5
update(3,4);   //a[3]=4
update(4,2);   //a[4]=2
update(5,3);   //a[5]=3
tree[1]=1
tree[2]=6     //含义 a1+a2 =1+5=6
tree[3]=4     //含义 a3 =4
tree[4]=12    //含义 a1+a2+a3+a4 =1+5+4+2=12
tree[5]=3    //含义 a5 =3

查询前缀和

复制代码
//查询前缀 [1,i] 的和
int query(int i)
{
    int res = 0;
    while(i > 0)
    {
        res += tree[i];
        i -= lowbit(i);
    }
    return res;
}

query不是读取tree1,tree2,tree3直接当元素,而是挑选若干块tree区间块相加拼出前缀和

query(3)求1~3总和:

复制代码
i=3,res += tree[3] → res=4; i=3‑lowbit(3)=2
i=2,res += tree[2] → res=4+6=10; i=2‑lowbit(2)=0停止
query(3)=tree[3]+tree[2];
query(7)=tree[7]+tree[6]+tree[4];

代码实现

复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500010;

int n, m;
int tree[N];

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

//单点增加:i位置加上delta,对应截图update
void update(int i, int delta)
{
    while(i <= n)
    {
        tree[i] += delta;
        i += lowbit(i);
    }
}

//查询前缀 [1,i] 的和
int query(int i)
{
    int res = 0;
    while(i > 0)
    {
        res += tree[i];
        i -= lowbit(i);
    }
    return res;
}

//求区间[l,r]和
int get_range(int l, int r)
{
    return query(r) - query(l-1);
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int val;
        cin >> val;
        update(i, val);
    }
    while(m--)
    {
        int op, x, y;
        cin >> op >> x >> y;
        if(op == 1)
        {
            //1 x k:x位置加上y
            update(x, y);
        }
        else
        {
            //2 x y 查询[x,y]
            cout << get_range(x, y) << '\n';
        }
    }
    return 0;
}

进阶1模板:区间加,单点查询

P3368 【模板】树状数组 2 - 洛谷

规则

操作1:1 x y k:区间 ( x , y ) (x,y) (x,y) 每个数加上 k k k

操作2:2 x:输出第 x x x 个元素的值

画图分析

差分思想

原数组 a a a,差分数组 d d d:

  • 区间 ( l , r ) (l,r) (l,r)加 k k k:d[l]+=k; d[r+1]-=k

  • a x = ∑ i = 1 x d i ax = \sum_{i=1}^x di ax=∑i=1xdi,也就是差分数组的前缀和就是原数组ax

单点增加

区间加等价:update(l,k); update(r+1,-k)

复制代码
if(op == 1)
      {
          //操作1:1 x y k,区间[x,y]加上k
          int x,y,k;
          cin >> x >> y >> k;
          update(x, k);
          update(y+1, -k);
      }

查询前缀和

查询单点ax等价:query(x)(求d的前缀和

复制代码
else
  {
     //操作2:2 x,输出a[x] = query(x),差分数组前缀和
      int x;
       cin >> x;
        cout << query(x) << '\n';
 }

注意:初始化: d 1 = a 1 , d i = a i − a i − 1 d1=a1,di=ai-ai-1 d1=a1,di=ai−ai−1,把差分数组丢进树状数组。

代码实现

复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500010;
int n, m;
int tree[N];
int a[N]; //保存原始输入数组

int lowbit(int x)
{
    return x & (-x);
}
//单点增加:i位置加上delta
void update(int i, int delta)
{
    while(i <= n)
    {
        tree[i] += delta;
        i += lowbit(i);
    }
}
//查询前缀 [1,i] 的和
int query(int i)
{
    int res = 0;
    while(i > 0)
    {
        res += tree[i];
        i -= lowbit(i);
    }
    return res;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    //初始化差分数组 d,树状数组维护d
    update(1, a[1]);
    for(int i = 2; i <= n; i++)
    {
        update(i, a[i] - a[i-1]);
    }

    while(m--)
    {
        int op;
        cin >> op;
        if(op == 1)
        {
            //操作1:1 x y k,区间\([x,y]\)加上k
            int x,y,k;
            cin >> x >> y >> k;
            update(x, k);
            update(y+1, -k);
        }
        else
        {
            //操作2:2 x,输出a[x] = query(x),差分数组前缀和
            int x;
            cin >> x;
            cout << query(x) << '\n';
        }
    }
    return 0;
}

进阶2模板:离散化 + 树状数组

P1908 逆序对 - 洛谷

相关推荐
每周报刊5 小时前
竞速座舱级视界:爱攻 AGP497UCZD 超宽 QDOLED 显示器解析
数据结构·游戏·计算机外设
小星星闪亮登场7 小时前
图论--最小生成树(内含二分图)
数据结构·算法·图论·迭代加深·图搜索算法
2601_962201728 小时前
Java进阶,集合,Colllection,常见数据结构
java·数据结构·windows
挽星安9 小时前
2026/8/29
数据结构·算法
positive_zpc9 小时前
进阶数据结构图——最短路径(二)
数据结构·算法·图论·最短路径
positive_zpc9 小时前
进阶数据结构图——最小生成树(一)
数据结构·算法·图论
码完就睡9 小时前
数据结构——遍历二叉树
数据结构·算法
老王爱玩车10 小时前
关于函数递归的优缺点分析
开发语言·数据结构·学习·算法
lv__pf10 小时前
Mysql索引优化实战1【TL mysql4】
数据结构