树状数组处理区间问题
树状数组是解决动态前缀和问题的数据结构,大佬们总是把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。
今天刚学,有点囫囵吞枣,描述的不太清楚。