树状数组和线段树

本文详细介绍了树状数组和线段树这两种数据结构,包括它们的基本概念、操作函数以及在动态求连续区间和、逆序对数量和数列区间最大值等问题中的应用。树状数组适用于区间查找和单点修改,线段树则能处理更复杂的问题,但实现相对复杂。文中提供了多个实例代码以帮助理解。
摘要由CSDN通过智能技术生成

写在前面

树状数组

  1. 树状数组和线段树用于区间查找(如同前缀和)和单点修改。不同之处是前缀和只能求区间和,而树状数组和线段树还可以动态修改单点值。
  2. 关于使用树状数组,脑袋里一定要有一个树状数组图像,细节操作(比如lowbit)不必理会。
  3. 树状数组必记3个函数——Lowbit函数(第x个数的上/下一个相关数是x ± (x & -x)),Add函数(在下标为x的地方加上v),Query函数(查询第1项到第x项的前缀和)。

树状数组示意图

线段树

  1. 关于线段树,一定要记住下面的图像。其中tr数组中每一个元素记录一个区间范围以及该区间的一个属性(区间和/区间最值等),因此tr元素应该具有三个属性:区间的左,右边界和区间的属性。
  2. 线段树有4个关键函数:PushUp函数, Build函数,Query函数和Modify函数。
    1. 每一个函数都有一个参数u,表示访问tr数组中第u个元素。
    2. Build函数的作用是建立所有树节点,并且规定每个结点所管辖的范围。同时还可以初始化一部分结点的属性,一定要先使用Build函数,然后才能进行后续操作。当然我们可以让Build函数只具有规定结点管辖范围的能力。
    3. PushUp函数一般用于Build函数和Modify函数中,当子结点的值改变后,需要通过该函数即时更新父结点的值。
    4. Modify函数的作用是给第x个加上v。
    5. Query函数的作用是查询区间left到right的某个属性(区间和/最值等)
  3. tr数组要开a数组的4倍!(别问为什么)
  4. 总体来说,线段树比树状数组的应用范围更广,但是难度较大,同时运行效率较低。我在代码中会标识出易错提醒。
    在这里插入图片描述

Acwing 1264. 动态求连续区间和

Acwing 1264. 动态求连续区间和

树状数组法:

#include<iostream>
using namespace std;

const int N = 100010;
int a[N], tr[N];
int n, m;

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

void Add(int x, int v) {
  for (int i = x; i <= n; i += Lowbit(i)) 
    tr[i] += v;
}

int Query(int x) {  //查询前x项数的和(包括第x项)
  int res = 0;
  for (int i = x; i; i -= Lowbit(i))
    res += tr[i];
  return res;
}

int main() {
  cin >> n >> m;
  
  //树状数组的值一般是从1开始
  for (int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
    Add(i, a[i]);
  }
  
  int k, a, b;
  while (m--) {
    scanf("%d%d%d", &k, &a, &b);
    if (!k) printf("%d\n", Query(b) - Query(a - 1));
    else Add(a, b);
  }
  
  return 0;
}

线段树法:

#include<iostream>
using namespace std;

const int N = 100010;
int a[N];

struct Node {
  int l, r, s;
}tr[4 * N];

void PushUp(int u) {
  tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
}

void Build(int u, int left, int right) {
  if (left == right) {
    tr[u] = { left, right, a[left] };  //易错
    return;
  }
  tr[u] = { left, right };  //易错
  int mid = left + right >> 1;
  Build(u << 1, left, mid), Build(u << 1 | 1, mid + 1, right);  //易错
  PushUp(u);  //易错
}

int Query(int u, int left, int right) {
  if (left <= tr[u].l && right >= tr[u].r)
    return tr[u].s;
  int sum = 0, mid = tr[u].l + tr[u].r >> 1;
  if (left <= mid) sum += Query(u << 1, left, right);  //易错
  if (right > mid) sum += Query(u << 1 | 1, left, right);  //易错
  return sum;
}

void Modify(int u, int x, int v) {
  if (tr[u].l == tr[u].r) {
    tr[u].s += v;
    return;
  }
  int mid = tr[u].l + tr[u].r >> 1;
  if (x <= mid) Modify(u << 1, x, v);  //易错
  else Modify(u << 1 | 1, x, v);  //易错
  PushUp(u);  //易错
}

int main() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++)
    scanf("%d", &a[i]);
  Build(1, 1, N);
  int k, a, b;
  while (m--) {
    scanf("%d%d%d", &k, &a, &b);
    if (!k) printf("%d\n", Query(1, a, b));
    else Modify(1, a, b);
  }
  return 0;
}

Acwing 788. 逆序对的数量

Acwing 788. 逆序对的数量

#include<iostream>
using namespace std;

const int N = 1000100;
int tr[N];

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

void Add(int x, int v) {
  for (int i = x; i <= N; i += Lowbit(i))
    tr[i] += v;
}

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

int main() {
  int n, tmp;
  long long res = 0;
  cin >> n;
  for (int i = 0; i < n; i++) {
    scanf("%d", &tmp);
    Add(tmp, 1);
    res += Query(N) - Query(tmp); 
  }
  cout << res << endl;
  return 0;
}

Acwing 1270. 数列区间最大值

Acwing 1270. 数列区间最大值
这道题只能用线段树解决!

#include<iostream>
using namespace std;

const int N = 100010;
int a[N];

struct Node {
  int left, right, maxv;
}tr[4 * N];

void pushup(int u) {
  tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv);
}

void build(int u, int left, int right) {
  if (left == right) {
    tr[u] = { left, right, a[left] };
    return;
  }
  tr[u] = { left, right };
  int mid = left + right >> 1;
  build(u << 1, left, mid), build(u << 1 | 1, mid + 1, right);
  pushup(u);
}

int query(int u, int left, int right) {
  if (left <= tr[u].left && right >= tr[u].right)
    return tr[u].maxv;
  int mid = tr[u].left + tr[u].right >> 1, maxv = 0;
  if (left <= mid) maxv = query(u << 1, left, right);
  if (right > mid) maxv = max(maxv, query(u << 1 | 1, left, right));
  return maxv;
}

int main() {
  int n, m;
  cin >> n >> m;
  
  for (int i = 1; i <= n; i++)
    scanf("%d", &a[i]);
  
  build(1, 1, n);
  
  while (m--) {
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d\n", query(1, a, b));
  }
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值