【复健】树状数组

树状数组复健

展开目录

Before

昨天机房讲树状数组的时候我在复健 MST,所以今天补一下树状数组。

发现自己以前写的学习笔记毫无逻辑,狗屁不通,根本读不懂。

本文自言自语含量大于学术性内容

树状数组(BIT)

一种支持 \(O(\log n)\) 复杂度单点修改与区间求和的数据结构。

树状数组并不是一种树形结构。

优点

修改与查询性能都比较优秀,比线段树省时省空间。码量小。

缺点

比线段树应用范围小,理解麻烦。

原理

用一些分散的区间和来储存数据。每个小区间储存数据的数量与其二进制最低位的 \(1\) 及其之后的 \(0\) 组成的数相等。

\[bit[(1100)_2] = a[1] + a[2] + ... + a[(100)_2] \]

可以考虑使用位运算获得最后一位 \(1\), 即 \(lowerbit\) 运算。

#define lowerbit(x) x & -x;

提供一种原理:计算机内有符号数以补码形式存储,当 \(x\) 是正数时, \(-x\) 会以其反码 \(+1\) 的形式储存。若 \(x\) 是奇数,即其补码尾数是 \(1\), 那么 \(-x\) 的反码尾数必定是 \(0\), 那么反码 \(+1\), 尾数一定也是 \(1\), 此时其它位都相反,x & -x 得到的就是尾数 \(1\).

同理可得偶数证明方法。

实现

区间 \(x\) 一定包含 \(x\), 包含 \(x\) 的区间之间相隔 \(lowebit(x)\) 个单位。

\(pos\) 为当前位置,则有:

单点修改:从当前位置开始,步长 \(lowerbit(pos)\), 将所有区间都加上/减去修改的变化量。

求前 \(n\) 项和:从 \(n\) 向下,步长 \(lowerbit(pos)\), 直到 \(pos < 1\) 为止。

利用这两项操作可以完成和树状数组有关的许多问题。

例题

【模板】树状数组1

题面:P3374

一道普通板子。

展开代码
#include <bits/stdc++.h>
#define ll long long
#define lowerbit(x) x & -x
#define MyWife Cristallo
using namespace std;
const int N = 1e5 + 5;
int num[N], bit[N], a, b, c, n, m;
char ch[6];
void update(int i, int x) { //i是位置,x是修改后的变化量
    for(int pos = i; pos <= n; pos += lowerbit(pos)) bit[pos] += x;
}
int iplus(int i) {
    int sum = 0;
    for(int pos = i; pos; pos -= lowerbit(pos)) sum += bit[pos];
    //第i个区间一定包含i,从i向下,每个包含i的区间相隔lowerbit(i)个单位长度
    return sum;
}
int lplusr(int l, int r) {
    return iplus(r) - iplus(l - 1);
}
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {scanf("%d", num + i); update(i, num[i]); }
    scanf("%d", &m);
    while(m--) {
        scanf("%s", ch + 1);
        if(ch[1] == 'A') {
            scanf("%d%d%d", &a, &b, &c);
            for(int i = a; i <= b; ++i) update(i, c);
        } 
        if(ch[1] == 'Q') {
            scanf("%d", &a);
            printf("%d\n", lplusr(a, a));
        }
    }
    return 0;
}

数列操作b

展开题面

假设有一列数 \({A_1, A_2, ..., A_i, ..., A_n}(1 \le i \le N, N \le 100000)\) ,支持如下两种操作:

  1. \(A_i\)\(A_j\) 的值均增加 \(D\). (\(i, j, D\) 是输入的数)

  2. 输出 \(A_i\). (\(i\) 是输入的数, \(i \le n\))

根据操作要求进行正确操作并输出结果。

P.S. 由于原题面 \(\LaTeX\) 崩坏过于严重,这里进行了一些修润。

一道板子,区间修改就是 for 循环 + 单点修改,单点查询就是长度为 \(1\) 的区间查询。

展开代码
#include <bits/stdc++.h>
#define ll long long
#define lowerbit(x) x & -x
#define MyWife Cristallo
using namespace std;
const int N = 1e5 + 5;
int num[N], bit[N], a, b, c, n, m;
char ch[6];
void update(int i, int x) { //i是位置,x是修改后的变化量
    for(int pos = i; pos <= n; pos += lowerbit(pos)) bit[pos] += x;
}
int iplus(int i) {
    int sum = 0;
    for(int pos = i; pos; pos -= lowerbit(pos)) sum += bit[pos];
    //第i个区间一定包含i,从i向下,每个包含i的区间相隔lowerbit(i)个单位长度
    return sum;
}
int lplusr(int l, int r) {
    return iplus(r) - iplus(l - 1);
}
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {scanf("%d", num + i); update(i, num[i]); }
    scanf("%d", &m);
    while(m--) {
        scanf("%s", ch + 1);
        if(ch[1] == 'A') {
            scanf("%d%d%d", &a, &b, &c);
            for(int i = a; i <= b; ++i) update(i, c);
        } 
        if(ch[1] == 'Q') {
            scanf("%d", &a);
            printf("%d\n", lplusr(a, a));
        }
    }
    return 0;
}

每天都在被自己的一些离谱错误气疯:

校门外的树

题面:Vijos P1448

开始想的是用树状数组求区间最值,后来发现没有原始数据求不了。抠了一天去看了个题解,说是可以用两个树状数组存每次修改的边界。

这谁能想出来啊,反正我不能。

展开代码
#include <bits/stdc++.h>
#define ll long long
#define lowerbit(x) x & -x
#define MyWife Cristallo
using namespace std;
const int N = 5 * 1e5 + 5;
int num[N], bit1[N], bit2[N], a, b, c, k, n, m;
void update1(int i, int x) { 
    for(int pos = i; pos <= n; pos += lowerbit(pos)) bit1[pos] += x;
}
void update2(int i, int x) { 
    for(int pos = i; pos <= n; pos += lowerbit(pos)) bit2[pos] += x;
}
int iplus1(int i) {
    int sum = 0;
    for(int pos = i; pos; pos -= lowerbit(pos)) sum += bit1[pos];
    return sum;
}
int iplus2(int i) {
    int sum = 0;
    for(int pos = i; pos; pos -= lowerbit(pos)) sum += bit2[pos];
    return sum;
}
int lplusr(int l, int r) {
    return iplus1(r) - iplus2(l - 1);
}
int main() {
    scanf("%d%d", &n, &m);
    while(m--) {
        scanf("%d%d%d", &k, &a, &b);
        if(k == 1) {update1(a, 1); update2(b, 1); }
        else printf("%d\n", lplusr(a, b));
    }
    return 0;
}

伟大的 2k3h 同志做出了这样的决定:

一键查询 Kiichi_ 精神状态:

posted @ 2023-07-31 11:59  _Kiichi  阅读(35)  评论(3编辑  收藏  举报