树状数组与线段树初步

树状数组与线段树初步

BIT(BinaryIndexedTree)

BIT的要求

普通的树状数组可维护的操作必须满足交换律可差分性

BIT能做什么

  • 单点修改,区间和
  • 区间修改,单点查询
  • 区间修改,区间和
  • 单点取max,前缀最大值

BIT的实现

首先,树状数组是一种基于二进制拆分的,支持单点修改区间查询的数据结构。

对于二进制拆分,我们有:

\(15 = 2^3 + 2^2 + 2^1 + 2^0\)

我们发现这个拆分和其最低位的1以及其后面的0形成的数有关,所先定义:

\(\operatorname{lowbit}(x) = x \ \& \ -x\)

那么我们就可以设计:

\(c[x]\) 管辖区间 \([x - lowbit(x) + 1, x]\) 的前缀和。

然后我们不难注意到:若修改 \(a[p]\) 的值,\(c[p + lowbit(p)]\)\(c[(p + lowbit(p)) + lowbit(p + lowbit(p))]...\) 的值将会被影响。然后好像可以写代码了。
很幽默的一点是之前我好像不会BIT,甚至不会背BIT板子((

所以我选择把板子贴在这:

struct BIT {
  int c[N];
  void add(int x, int val) {
    while (x <= n) {
      c[x] += val;
      x += x & -x;
    }
    return ;
  }
  int query(int x) {
    int sum = 0;
    while (x) {
      sum += c[x];
      x -= x & -x;
    }
    return sum;
  }
};

以上代码实现了单点修改区间求和的功能

例题:AT_dp_q Flowers

Solution

注意到这其实是带权的最长上升子序列。设 \(dp[i]\) 表示以 \(i\) 结尾的序列的美力值的最大值,于是不难写出转移方程:

\[dp[i] = a[i] + \max\{ dp[j] \mid j < i \text{ 且 } h[j] < h[i] \} \]

暴力转移的复杂度是 \(O(N^2)\),考虑优化。

按顺序扫描, \(j < i\) 自动满足。用树状数组维护按高度查询前缀最大值\(O(N \log N)\) 解决。

Code
#include <bits/stdc++.h>

using namespace std;
using ll = long long;
const int N = 2e5 + 10;

int n;

struct BIT {
  ll c[N];
  void update(int x, ll val) {
    while (x <= n) {
      c[x] = max(val, c[x]);
      x += x & -x;
    }
    return ;
  }
  ll query(int x) {
    ll sum = 0;
    while (x) {
      sum = max(sum, c[x]);
      x -= x & -x;
    }
    return sum;
  }
};

BIT bit;
ll h[N], a[N];

int main() {
  cin >> n;
  for (int i = 1; i <= n; ++i)
    cin >> h[i];
  for (int i = 1; i <= n; ++i)
    cin >> a[i];
  ll ans = 0;
  for (int i = 1; i <= n; ++i) {
    ll tmp = bit.query(h[i] - 1) + a[i];
    bit.update(h[i], tmp);
    ans = max(ans, tmp);
  }
  cout << ans << '\n';
  return 0;
}

复杂度

时间:\(O(N \log N)\)

空间:\(O(N)\)

SGT(SegmentTree)

SGT的要求以及SGT能做什么

线段树是个强力的维护区间信息的数据结构,BIT能做的它都能做,BIT不能做的它有的也能做((

SGT的实现

感觉挺好理解以及背诵,这边就不写了,放一个洛谷上线段树1的代码:

Code
struct SGT {
  ll tr[N << 2], lzy[N << 2];
  void build(int l, int r, int p) {
    if (l == r) {
      tr[p] = a[l];
      return ;
    }
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    build(l, mid, ls);
    build(mid + 1, r, rs);
    tr[p] = tr[ls] + tr[rs]; 
  }
  void spread(int l, int r, int p) {
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    tr[ls] += (mid - l + 1) * lzy[p], lzy[ls] += lzy[p];
    tr[rs] += (r - mid) * lzy[p], lzy[rs] += lzy[p];
    lzy[p] = 0;
  }
  ll query(int l, int r, int x, int y, int p) {
    if (x <= l && r <= y) return tr[p];
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    spread(l, r, p);
    ll ans = 0;
    if (x <= mid) ans += query(l, mid, x, y, ls);
    if (y >= mid + 1) ans += query(mid + 1, r, x, y, rs);
    return ans;
  }
  void update(int l, int r, int x, int y, int p, ll val) {
    if (x <= l && r <= y) {
      tr[p] += (r - l + 1) * val, lzy[p] += val;
      return ;
    }
    spread(l, r, p);
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    if (x <= mid) update(l, mid, x, y, ls, val);
    if (y >= mid + 1) update(mid + 1, r, x, y, rs, val);
    tr[p] = tr[ls] + tr[rs];
  }
};

例题1:小白逛公园

Solution

不难发现这题实际上维护的是单点修改与区间最大子段和的查询。区间最大值很显然不能直接做的,我们考虑维护各个区间的总和,前缀最大值,后缀最大值,从而得到区间最大子段和。

Code
#include <bits/stdc++.h>

using namespace std;
const int N = 5e5 + 10;

int a[N];

// 考虑求区间最大子段和没有可合并性,故需要维护区间的前缀最大值,后缀最大值,以及区间和和最大子段和并并

struct SGT {
  struct Node {
    int lmax, rmax, sum, maxx;
  } tr[N << 2];
  Node merge(Node l, Node r) {
    Node res;
    res.sum = l.sum + r.sum;
    res.lmax = max(l.lmax, l.sum + r.lmax);
    res.rmax = max(r.rmax, r.sum + l.rmax);
    res.maxx = max({l.maxx, r.maxx, l.rmax + r.lmax});
    return res;
  }
  void build(int l, int r, int p) {
    if (l == r) {
      tr[p].sum = tr[p].lmax = tr[p].rmax = tr[p].maxx = a[l];
      return ;
    }
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    build(l, mid, ls);
    build(mid + 1, r, rs);
    tr[p] = merge(tr[ls], tr[rs]);
  }
  // 在单点修改 x 的时候,所有包含 x 的区间都要对应修改,这些区间恰好是在定位 x 时遇到的区间
  void update(int l, int r, int x, int val, int p) {
    if (l == r) return tr[p].sum = tr[p].lmax = tr[p].rmax = tr[p].maxx = val, void();
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    // spread(l, r, p);
    if (x <= mid) update(l, mid, x, val, ls);
    else update(mid + 1, r, x, val, rs);
    tr[p] = merge(tr[ls], tr[rs]); 
  }
  Node query(int l, int r, int x, int y, int p) {
    if (x <= l && r <= y) return tr[p];
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    if (y <= mid) return query(l, mid, x, y, ls);
    if (x >= mid + 1) return query(mid + 1, r, x, y, rs);
    Node le = query(l, mid, x, y, ls), ri =  query(mid + 1, r, x, y, rs);
    return merge(le, ri);
  }
};

SGT sgt;
int n, m;

int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; ++i)
    cin >> a[i];
  sgt.build(1, n, 1);
  while (m--) {
    int op, x, y; cin >> op >> x >> y;
    if (op == 1) {
      if (x > y) swap(x, y);
      cout << sgt.query(1, n, x, y, 1).maxx << '\n';
    } else if (op == 2)
      sgt.update(1, n, x, y, 1);
  }
  return 0;
}

例题2: P4145 上帝造题的七分钟 2 / 花神游历各国

Solution

注意到 \(\sqrt1 = 1\),这等价于不进行操作。故用一个不需要下传的剪枝标记(待指正)标记出数字为 \(1\) 的段即可。

Code
#include <bits/stdc++.h>

using namespace std;
using ll = long long;
const int N = 1e5 + 10;

ll a[N];

struct SGT {
  ll tr[N << 2];
  bool tag[N << 2];
  void build(int l, int r, int p) {
    if (l == r) return tr[p] = a[l], tag[p] = (a[l] == 1), void();
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    build(l, mid, ls);
    build(mid + 1, r, rs);
    tr[p] = tr[ls] + tr[rs];
    tag[p] = tag[ls] && tag[rs];
  }
  void update(int l, int r, int x, int y, int p) {
    if (tag[p]) return ;
    if (l == r) return tr[p] = sqrt(tr[p]), tag[p] = (tr[p] == 1), void();
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    if (x <= mid) update(l, mid, x, y, ls);
    if (y >= mid + 1) update(mid + 1, r, x, y, rs);
    tr[p] = tr[ls] + tr[rs];
    tag[p] = tag[ls] && tag[rs];
  }
  ll query(int l, int r, int x, int y, int p) {
    if (x <= l && r <= y) return tr[p];
    int mid = (l + r) >> 1, ls = p << 1, rs = p << 1 | 1;
    ll ans = 0;
    if (x <= mid) ans += query(l, mid, x, y, ls);
    if (y >= mid + 1) ans += query(mid + 1, r, x, y, rs);
    return ans;
  }
};

SGT sgt;
int n, m;

int main() {
  cin >> n;
  for (int i = 1; i <= n; ++i)
    cin >> a[i];
  sgt.build(1, n, 1);
  cin >> m;
  while (m--) {
    int op, l, r;
    cin >> op >> l >> r;
    if (l > r) swap(l, r);
    if (op == 0) sgt.update(1, n, l, r, 1);
    else cout << sgt.query(1, n, l, r, 1) << '\n';
  }
  return 0;
}
posted @ 2026-07-18 20:33  _SeaWave_R  阅读(5)  评论(0)    收藏  举报