LOADING

加载过慢请开启缓存 浏览器默认开启

树状数组入门

树状数组处理区间问题

树状数组是解决动态前缀和问题的数据结构,大佬们总是把ST表,树状数组,线段树三个东西联系在一起,都是用于解决动态区间问题的方法。

树状数组是2的次方前缀和数组,像树一样,如图所示,每个位置 i 的前缀和长度为 lowbit(i),而每层之间深度所差的区间长度也为lowbit(i),例如要对 t[4] +1,则其后面的包含 t[4] 的 t[8]也进行+1。

因此当我们要求 6的前缀和,因该是6所包含的区间加上6包含区间的前面的区间,即是 t[4] + t[6]。

若求 3 到 6的区间和时,便是 前缀和 6 - 前缀和 2,而 前缀和 6 = t[6] +  t[4] ,所以3 到 6 的区间和便是  t[6] +  t[4] - t[2]       

单点加值:

//包含a点的父亲位置都要加上b
void add(int a,int b){
    for(int i=a;i<=n;i+=lowbit(i))
        t[i]+=b;
}

 前缀和:

//x所包含的区间和 + x区间前面所包含的区间和
//x区间的长度为lowbit(x)
int sum(int x){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=t[i];
    return ans;
}

下面就是单点改值和区间和查询的问题

P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态

题目描述
如题,已知一个数列,你需要进行下面两种操作:

输入格式
第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

这样子这道题就变得显而易见了

lowbit(x) 的返回值是x二进制最小位1,所以返回的是x&-x,这里我用宏定义
对于lowbit(x), 我们从已学的计算机理论知识中得知计算机对两个数进行计算时,是将其转换为补码进行运算,而正数的补码又是其本身.

假设我们有一个整数 $x = (0 1 0 1 0)_B$, 我们有以下操作
$ x = (0 1 0 1 0)_B$ // x 的原码表示
$-x = (1 1 0 1 0)_B$ //-x 的原码表示
$-x = (1 0 1 1 0)_B$ //-x 的补码表示
$ x $&$ (x) = $ //x 与 -x 按位与
$ (0 1 0 1 0)_B$ //x 的补码
& $(1 0 1 1 0)_B$ //-x 的补码
$=$ $(0 0 0 1 0)_B$ //按位与后的补码(同时也是原码)
于是我们便可以得出x在二进制位下最小1所表示的数值

#include <bits/stdc++.h>
#define rep(i,n,m) for(int i=n;i<=m;++i)
#define per(i,n,m) for(int i=n;i>=m;--i)
#define PII pair<int,int>
#define lowbit(x) x&-x
#define INF 1<<30
using namespace std;
typedef long long ll;
const int N=5e5+10;
int t[N],n,q;
void add(int a,int b){
    //父亲节点都 +b
    for(int i=a;i<=n;i+=lowbit(i))t[i]+=b;
}
int sum(int x){
    int ans=0;
    前面每一段区间和
    for(int i=x;i>0;i-=lowbit(i))ans+=t[i];
    return ans;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>q;
    rep(i,1,n){
        int x;cin>>x;
        add(i,x);
    }
    while(q--){
        int a,b,c;cin>>a>>b>>c;
        if(a==1)
            add(b,c);
        else 
            cout<<sum(c)-sum(b-1)<<endl;
            //前缀和差
    }
    return 0;
}

还有区间改值和单点查询的问题,

P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态

题目描述
如题,已知一个数列,你需要进行下面两种操作:

输入格式
第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 M 行每行包含 2 或 4 个整数,表示一个操作,具体如下:

操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 k;

操作 2: 格式:2 x 含义:输出第 x 个数的值。

下标i    0  1  2  3  4  5  6  7    

原数值a  0  1  2  1 -2  4  9  0

差分值b  0  1  1 -1 -3  6  5 -9

对于区间改值后的区间和,我们知道可以用差分做,那差分是什么呢,

差分 b[i] 就是 a[i] - a[i-1]  ,而 a[n] 可以用 表示(哈哈哈,公式好大个),当区间 [l,r] 内+x时,只需 b[l] +x,b[r+1] -x,即可保证只有区间内值增加

对于该题,我们不妨初始化差分数组为0,然后使次区间改值时对差分数组边界进行两次操作 

最后的单点查询就是改变的差分值加上原数值,即 sum(x) + a[x] 

(以下用t数组代表差分数组,q数组表示原数值数组)

#include <bits/stdc++.h>
#define rep(i,n,m) for(int i=n;i<=m;++i)
#define per(i,n,m) for(int i=n;i>=m;--i)
#define PII pair<int,int>
#define lowbit(x)  x&-x
#define INF 1<<30
using namespace std;
typedef long long ll;
const int N=5e5+10;
int q[N],t[N],n,T;
void add(int a,int b){
    for(int i=a;i<=n;i+=lowbit(i))t[i]+=b;
}
int sum(int x){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))ans+=t[i];
    return ans;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>T;
    rep(i,1,n)cin>>q[i];
    while(T--){
        int x,a,b,c;cin>>x;
        if(x==1){
            cin>>a>>b>>c;
            //差分边界进行操作
            add(a,c);
            add(b+1,-c);
        }else {
            cin>>a;
            //单点查询值=差分数组前缀和+原数值
            cout<<sum(a)+q[a]<<endl;   
        }
    }
    return 0;
}

对于某些会越界的样例,必要时要int改long long。

今天刚学,有点囫囵吞枣,描述的不太清楚。