一维树状数组
学习来自https://www.bilibili.com/video/av18735440?from=search&seid=12510069409139918776电子科技大学算法讲堂
https://www.cnblogs.com/RabbitHu/p/BIT.html大佬的文章,侵删,本文推理都来自该文。
这里d数组是我的树状数组,a数组就是初始数组了,让你修改查询的
这就是树状数组的存储方式,不难看出它的存储是和二进制有关的(滑稽,好看出个鬼)举个栗子d[6]=a[5]+a[6]; (6的二进制数是110,末尾一个0,那d[6]就是存储2^1个元素的和) 同理我们也可以验证d[8]=a[1]+...a[8](因为8的二进制数是1000,有3个零,就是存储2^3个) ; 而查询前缀和,就需要用到lowbit这个函数,非常巧妙,我说不太清,看视频说的非常清楚。
int lowbit(int x){
return x&(-x);
}
x-=lowbit(x);
or x+=lowbit(x);
也可以直接使用 x+=x&(-x) or x-=x&(-x)
比如查询13这个位置的前缀和 13的二进制数为1101,我们将其拆分为(lowbit就是在此起着至关重要的作用,取得最后一个1)
1101 d[13] a[13] //从后往前以1为末尾,后面全是0
1100 d[12] a[9]+a[12]
1000 d[8] a[1]+...a[8]
所以前13的前缀和就是 d[13] +d[12] +d[8]
而修改和查询的顺序是反的。
这里我搬运了大佬的代码(笑嘻嘻)
单点修改+区间查询
//单点修改+区间查询
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e7+5;
ll sum[maxn],n;//数组从1开始到n
void add(int p, int x){ //给位置p增加x
while(p <= n) sum[p] += x, p += p & -p;
}
ll ask(int p){ //求位置p的前缀和
ll res = 0;
while(p) res += sum[p], p -= p & -p;
return res;
}
ll range_ask(int l, int r){ //区间求和
return ask(r) - ask(l - 1);
}
int main(){
return 0;
}
而区间修改的话,其实就是用了一个差分的思想了;
比如 a数组 1 1 1 1 1
b数组 1 0 0 0 0 b[n]=a[n]-a[n-1]
我想要再a数组的2~3都加上2;
a数组 1 3 3 3 1
b数组 1 2 0 0 -2 我们发现只要修改b数组两个元素就可以来维护a数组了
a数组就是b数组的前缀和
所以区间修改 只要修改俩个元素就行了(舒舒服服)。
区间修改+单点查询
(这里都搬运的大佬的详解)
//区间修改+单点查询
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e7+5;//这里可以理解成 原数组是sum数组的前缀和
ll sum[maxn],n;//数组从1开始到n
void add(int p, int x){ //这个函数用来在树状数组中直接修改
while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){ //给区间[l, r]加上x
add(l, x), add(r + 1, -x);
}
int ask(int p){ //单点查询
ll res = 0;
while(p) res += sum[p], p -= p & -p;
return res;
}
int main(){
return 0;
}
区间修改+区间查询
//区间修改+区间查询
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e7+5;
ll d[maxn];//记录原数组的前缀和
ll sum1[maxn];
ll sum2[maxn];
ll n,M;
void add(ll p, ll x){
for(int i = p; i <= n; i += i & -i)
sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
add(l, x), add(r + 1, -x);
}
ll ask(ll p){
ll res = 0;
for(int i = p; i; i -= i & -i)
res += (p + 1) * sum1[i] - sum2[i];
return res;
}
ll range_ask(ll l, ll r){//l到r的区间和
return ask(r) - ask(l - 1);
}
int main(){
//先不考虑原数组的影响,直接区间修改修改sum1和sum2
//最后要求[l,r]的区间和的话
//结果就是range_ask(l,r)+d[r]-d[l-1]
// d[r]-d[l-1]考虑原数组的前缀和
return 0;
}
-
二维树状数组
如果不是做题,谁会作死搞二维数组的。
在一维树状数组中,tree[x](树状数组中的那个“数组”)记录的是右端点为x、长度为lowbit(x)的区间的区间和。
那么在二维树状数组中,可以类似地定义tree[x][y]记录的是右下角为(x, y),高为lowbit(x), 宽为 lowbit(y)的区间的区间和。
这个还是比较好理解的。
单点修改 + 区间查询
#include<bits/stdc++.h>
#define il inline
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
int sum[maxn][maxn],n;
il void add(int x,int y,int z){//将(x,y) +x
int py=y;
while(x<=n+3){
y=py;
while(y<=n+3) sum[x][y]+=z,y+=y&-y;
x+=x&-x;
}
}
il ll ask(int x,int y){//求左上(1,1)右下(x,y)的矩阵和
ll res=0,py=y;
while(x){
y=py;
while(y) res+=sum[x][y],y-=y&-y;
x-=x&-x;
}
return res;
}
int main(){
std::ios::sync_with_stdio(0);
return 0;
}
区间修改 + 单点查询
#include<bits/stdc++.h>
#define il inline
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
int sum[maxn][maxn],n;
il void add(int x,int y,int z){
int py=y;
while(x<=n+3){
y=py;
while(y<=n+3) sum[x][y]+=z,y+=y&-y;
x+=x&-x;
}
}
il void r_add(int xa,int ya,int xb,int yb,int z){//给左上(xa,ya)右下(xb,yb)矩阵+z
add(xa,ya,z),add(xa,yb+1,-z),add(xb+1,ya,-z),add(xb+1,yb+z,z);
}
il ll ask(int x,int y){//查询(x,y)这点的值
ll res=0,py=y;
while(x){
y=py;
while(y) res+=sum[x][y],y-=y&-y;
x-=x&-x;
}
return res;
}
int main(){
return 0;
}
区间修改 + 区间查询
#include<bits/stdc++.h>
#define il inline
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
ll t1[maxn][maxn],t2[maxn][maxn],t3[maxn][maxn],t4[maxn][maxn];
int n;
il void add(int x,int y,int z){
int py=y;
while(x<=n+3){
y=py;
while(y<=n+3){
t1[x][y]+=z,t2[x][y]+=z*x,t3[x][y]+=z*y,t4[x][y]+=z*x*y;
y+=y&-y;
}
x+=x&-x;
}
}
il void r_add(int xa,int ya,int xb,int yb,int z){
add(xa,ya,z),add(xa,yb+1,-z),add(xb+1,ya,-z),add(xb+1,yb+1,z);
}
il ll ask(int x,int y){
ll res=0,py=y;
while(x){
y=py;
while(y){
res+=(x+1)*(y+1)*t1[x][y]-(y+1)*t2[x][y];
res+=-(x+1)*t3[x][y]+t4[x][y];
y-=y&-y;
}
x-=x&-x;
}
return res;
}
il ll r_ask(int xa,int ya,int xb,int yb){
return ask(xb,yb)-ask(xb,ya-1)-ask(xa-1,yb)+ask(xa-1,ya-1);
}
int main(){
return 0;
}
-
再补上三维树状数组
区间修改+单点查询
原理和二维一样不过就是再多加了一维。
//区间修改+单点查询
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
#define sc(n) scanf("%d",&n)
#define SC(n,m) scanf("%d %d",&n,&m)
#define SZ(a) int((a).size())
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-9;
const int maxn=105;
int sum[maxn][maxn][maxn],n;
il void add(int x,int y,int z,int w) {
for(int i=x; i<=n+2; i+=i&-i) {
for(int j=y; j<=n+2; j+=j&-j) {
for(int k=z; k<=n+2; k+=k&-k)
sum[i][j][k]+=w;
}
}
}
il void r_add(int x1,int y1,int z1,int x2,int y2,int z2,int w) {
add(x1,y1,z1,w);
add(x1,y2+1,z1,-w);
add(x2+1,y1,z1,-w);
add(x2+1,y2+1,z1,w);
add(x1,y1,z2+1,-w);
add(x1,y2+1,z2+1,w);
add(x2+1,y1,z2+1,w);
add(x2+1,y2+1,z2+1,-w);
}
il int query(int x,int y,int z){
int res=0;
for(int i=x;i;i-=i&-i){
for(int j=y;j;j-=j&-j){
for(int k=z;k;k-=k&-k)
res+=sum[i][j][k];
}
}
return res;
}
int main() {
std::ios::sync_with_stdio(0);
return 0;
}
其他的还没遇到//
最后膜巨佬!Orz
完结撒花!!!!