树状数组

前置知识

\(\operatorname{lowbit}(x)\) 表示 \(x\) 在二进制下最后一个 \(1\) 的位权(例如 \(\operatorname{lowbit}(6)=\operatorname{lowbit}(110_2)=2\)),那我们该怎么计算呢?

注意:\(\operatorname{lowbit}(x)\) 并不是指位数 \(k\),而是位权 \(2^k\)

首先我们得知道计算机内部是如何存储数字的(这里以八位二进制为例)。

位号 \(\color{red}7\) \(6\) \(5\) \(4\) \(3\) \(2\) \(1\) \(0\)
位权 \(\color{red} -2^7\) \(2^6\) \(2^5\) \(2^4\) \(2^3\) \(2^2\) \(2^1\) \(2^0\)

注意在这里最高位上的位权是负的。

举个例子:\(-20\) 在计算机中是 \(11110110_2\)

可以发现,任何一个数 \(x\)\(-x\) 在计算机中的值作位与运算刚好等于 \(\operatorname{lowbit}(x)\)

可以使用构造证明。因为 \(x+(-x)=0\),也就是让他们刚好进位到最高位,再进位到前面不存在的那位,最终才会得到 \(0\),也就是说,\(-x\) 的第 \(i\)\(=(x\) 的第 \(i\)\(+\) 上一位的进位 \() \bmod 2\),所以只有 \(\operatorname{lowbit} (x)\) 那一位上 \(x\)\(-x\) 都为一,因为在 \(\operatorname{lowbit} (x)\) 的那一位之前都没有进位。

代码

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

树状数组

简介

树状数组是一种可以 \(O(N)\) 建树,\(O(\log N)\) 单点修改前缀查询的数据结构,但是由于只能查询前缀,所以要维护的信息必须有逆运算并且满足结合律。树状数组能做的线段树都能做,但线段树能做的树状数组不一定能做,不过树状数组相对来说码量较少,常数也更小。

在树状数组中,每个节点都代表一个区间,节点 \(i\) 对应的区间为 \([i-\operatorname{lowbit} (i)+1,i]\),树状数组可以将一段前缀分解成不超过 \(\log N\) 段来求解。

例如当 \(N=8,A=\{3,1,4,1,5,9,2,6\}\) 时树状数组如下:

每个节点都代表一个区间,其中最下面一排为 \(A\),红色为和,绿色为管辖区间,黑色为编号。

建树

首先的问题就是:\(i\) 的父亲(即最小的一个不为 \(i\) 的将 \(i\) 包含的节点)到底是多少?

先证明一些性质。

\(l(x)=x-\operatorname{lowbit}(x)+1\),即 \(x\) 的左端点,\(c(x)=[l(x),x]\),即 \(x\) 对应的区间。

性质一

证:对于 \(x \le y\),要么有 \(c(x)\)\(c(y)\) 不交,要么有 \(c(x)\) 包含于 \(c(y)\)

假设 \(x\)\(y\) 相交,则有 \(l(y) \le x \le y\)

\(y=s \cdot 2^{k+1} + 2^k\),则 \(l(y)=s \cdot 2^{k+1}+1\)

\(\therefore x\) 可以表示为 \(s \cdot 2^{k+1}+b\),其中 \(1 \le b \le 2^k\)

\(\therefore \operatorname{lowbit} (x)=\operatorname{lowbit} (b)\)

\(\because b-\operatorname{lowbit}(b) \ge 0\)

\(\therefore l(x)=x-\operatorname{lowbit}(x)+1=s \cdot 2^{k+1}+b-\operatorname{lowbit}(b)+1 \ge s \cdot 2^{k+1}+1=l(y)\),即 \(l(y) \le l(x) \le x \le y\)

\(\therefore\) 如果 \(c(x),c(y)\) 相交,则 \(c(x)\) 一定包含于 \(c(y)\)

性质二

证:\(c(x)\) 真包含于 \(c(x + \operatorname{lowbit}(x))\)

\(y=x+\operatorname{lowbit}(x)\)\(x=s \cdot 2^{k+1}+2^{k}\)

\(\therefore y = (s+1)\cdot 2^{k+1},l(x)=s\cdot 2^{k+1}+1\)

\(\because \operatorname{lowbit}(y) \ge 2^{k+1}\)

\(\therefore l(y)=(s+1) \cdot 2^{k+1}-\operatorname{lowbit}(y)+1 \le s \cdot 2^{k+1}+1=l(x)\),即 \(l(y) \le l(x) \le x \le y\)

\(\therefore c(x)\) 真包含与 \(c(x + \operatorname{lowbit}(x))\)

性质三

证:对于任意 \(x < y < x + \operatorname{lowbit}(x)\),有 \(c(x)\)\(c(y)\) 不交。

\(x=s \cdot 2^{k+1}+2^{k}\),则 \(y=x+b=s \cdot 2^{k+1}+2^{k}+b\),其中 \(1 \le b < 2^{k}\)

不难发现 \(\operatorname{lowbit}(y)=\operatorname{lowbit}(b)\)

\(\because b - \operatorname{lowbit}(b)\ge0\)

\(\therefore l(y)=y-\operatorname{lowbit}(y)+1=x+b-\operatorname{lowbit}(b)+1>x\),即 \(l(x) \le x < l(y) \le y\)

\(\therefore c(x) 与 c(y)\) 不交。

结论

由于上面这些性质,我们很容易得到 \(i\) 的父亲就是 \(i+\operatorname{lowbit}(i)\)

所以直接累加答案即可。

时间复杂度 \(O(N)\)

代码

for(int i = 1; i <= n; ++i) {
  tr[i] += a[i];
  tr[i + lowbit(i)] += tr[i];
}

前缀查询

由于第 \(i\) 个节点的管辖区间是 \([i-\operatorname{lowbit}(i)+1,i]\),所以如果令 \(g(i)=\sum \limits_{i=1}^i A_i\),则 \(g(i)\) 还可以写作 \(g(i)=f_i+g(i-\operatorname{lowbit}(i))\)

由于在二进制下最多有 \(\log N\) 位,每次减去 \(\operatorname{lowbit}(i)\),所以总时间复杂度为 \(O(\log N)\)

代码

int getsum(int p) {
  int sum = 0;
  for(; p; sum += tr[p], p -= lowbit(p)) {
  }
  return sum;
}

getsum(r) - getsum(l - 1);

单点修改

单点修改也很简单,由于 \(i\) 的父亲是 \(i+\operatorname{lowbit}(i)\),所以将修改的节点的所有祖先累加答案即可。

由于每次 \(+\operatorname{lowbit}(i)\),至少使 \(\operatorname{lowbit}(i)\times 2\),即进一位,所以单词修改时间复杂度 \(O(\log N)\)

代码

void update(int p, int x) {
  for(; p <= n; tr[p] += x, p += lowbit(p)) {
  }
}

update(p, x);

区间修改、单点查询

实际上就是把树状数组当成差分数组罢了。

代码

for(int i = 1; i <= n; ++i) {
  cin >> a[i];
  update(i, a[i]), update(i + 1, -a[i]);
}

update(l, x), update(r + 1, -x);

getsum(p);

代码

区间查询、单点修改

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

const int MAXN = 200001;

int n, q, a[MAXN], tr[MAXN];

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

void build() {
  for(int i = 1; i <= n; ++i) {
    tr[i] += a[i];
    tr[i + lowbit(i)] += tr[i];
  }
}

void update(int p, int x) {
  for(; p <= n; tr[p] += x, p += lowbit(p)) {
  }
}

int getsum(int p) {
  int sum = 0;
  for(; p; sum += tr[p], p -= lowbit(p)) {
  }
  return sum;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
  }
  build();
  cin >> q;
  for(int i = 1, op, p, x, l, r; i <= q; ++i) {
    cin >> op;
    if(op == 1) {
      cin >> p >> x;
      update(p, x);
    }else {
      cin >> l >> r;
      cout << getsum(r) - getsum(l - 1) << "\n";
    }
  }
  return 0;
}

区间修改、单点查询

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

const int MAXN = 200005;

int n, q, a[MAXN], tr[MAXN];

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

void update(int p, int x) {
  for(; p <= n; tr[p] += x, p += lowbit(p)) {
  }
}

int getsum(int p) {
  int sum = 0;
  for(; p; sum += tr[p], p -= lowbit(p)) {
  }
  return sum;
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
    update(i, a[i]), update(i + 1, -a[i]);
  }
  cin >> q;
  for(int i = 1, op, p, x, l, r; i <= q; ++i) {
    cin >> op;
    if(op == 1) {
      cin >> l >> r >> x;
      update(l, x), update(r + 1, -x);
    }else {
      cin >> p;
      cout << getsum(p) << "\n";
    }
  }
  return 0;
}
posted @ 2024-04-16 17:17  Yaosicheng124  阅读(6)  评论(0编辑  收藏  举报