首先看一下朴素算法进行下面两个操作的时间复杂度
区间查询 O(n) 单值修改O(1)
如果进行n次查询,那么时间复杂度将是不可接受的
其次看一下前缀和数组的时间复杂度
区间查询 O(1) 单值修改O(n)
同样进行n次修改,时间复杂度也是不可接受的
树状数组则是在这两者中做了折中,实现了两者均为log(n)的时间复杂度
区间查询 O(log(n)) 单值修改O(log(n))
lowbit
树状数组所学的预备知识
lowbit即求一个数的二进制表示的最低位的1
int lowbit(int x){ return x&(-x); } //x&(~x+1) ~x+1=-x
树状数组
美妙的规律
下面构造数列t[x](前缀和的树状表示)
何为以x为根的子树种叶节点的值
注意如t[4],以t[4]为根的叶分别是t[2],t[3],a[4],也就是t[4]=t[2]+t[3]+a[4]
再来观察下t[x]所表示的区间——是以a[x]为结尾,长度为lowbit(x)的区间
观察上图 lowbit(1)=lowbit(3)=lowbit(5)=lowbit(7)=1
lowbit(2)=lowbit(6)=2 lowbit(4)=4 lowbit(8)=8
并且我们可以发现一个子叶x的父节点就是x+lowbit(x) ----------------(1)
掌握了上述规律后,就可以开始研究两个操作了:单点修改,区间求和
单点修改&&区间查询
单点修改
要修改a[x],也就是我们要将所有涵a[x]这个元素的t[i]修改了
注意下图,我们只需要找到最小的涵a[x]的子叶,然后再通过其找到父节点
下面给出代码 (运用规律(1),因为要通过子节点找父节点)
void add(int x,int k){
while(x<=n){ t[x]+=k; x+=lowbit(x);}
}
区间查询
区间求和我们可以分两步
1.得到左右端点的前缀和 2.做差
int ask(int x){
int ans=0;
while(x){ ans+=t[x]; x-=lowbit(x); }
}
区间修改&&单点查询
让我们再研究两个操作:区间修改,单点查询;这时要引入增量数组b
用树状数组维护b的前缀和,即a[]每个元素的增量
[l,r]+k add(l,k) add(r+1,-k)
查询a[x] ans=a[x]+ask(x)
区间修改&&区间查询
使用树状数组当然也是可以的,但是更好的当然是线段树了(之后再补)
注意到下图中蓝色区域就是我们要求的
这样我们可以分别用2个树状数组t1,t2维护b[i]和i*b[i]的前缀和
也就得到了下面公式了
例题
[模板]树状数组1
(P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
#include<bits/stdc++.h>
using namespace std;
int n,m,op,x,y;sum[500010],a[500010],b[500010];//sum表示a的前缀和用于初始化b
int lowbit(int x){ return (x&-x); }
void inti(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=n;i++){
b[i]=sum[i]-sum[i-lowbit(i)];
}
}
int ask(int x){//表示ai的前缀和
int ans=0;
while(x){ ans+=b[x]; x-=lowbit(x); }
}
void add(int x,int k){
while(x<=n){ b[x]+=k; x+=lowbit[x]; }
}
int main(){
inti();
for(int i=0;i<m;i++){
cin>>op>>x>>y;
if(op==1)add(x,y);
else cout<<ask(y)-ask(x-1)<<endl;
}
return 0;
}
[模板]树状数组2
#include<bits/stdc++.h>
using namespace std;
int lowbit(int x){ return (x&-x); }
//sum表示dis的前缀和,dis表示a的差分,a用于初始化b(树状数组)
int n,m,op,x,y,k,dis[500010],a[500010],b[500010],sum[500010];
void inti(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
dis[i]=a[i]-a[i-1];
sum[i]=dis[i]+sum[i-1];
}
for(int i=1;i<=n;i++){
b[i]=sum[i]-sum[i-lowbit(i)];
}
}
int ask(int x){//表示a[i]
int ans=0;
while(x){ ans+=b[x]; x-=lowbit(x); }
return ans;
}
void add(int x,int k){
while(x<=n){ b[x]+=k; x+=lowbit(x); }
}
int main(){
inti();
for(int i=0;i<m;i++){
cin>>op;
if(op==1){
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
else {
cin>>x;
cout<<ask(x)<<endl;
}
}
return 0;
}
P5057 [CQOI2006]简单题
P5057 [CQOI2006]简单题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
首先用树状数组维护a的差分数组,a[i]表示元素i被修改了几次(那么偶数次为0,奇数次为1)
也就是ask(x)即得到a[i],对[l,r] +1,就是add(l,1),add(r+1,-1)
#include<bits/stdc++.h>
using namespace std;
int n,m,op,x,y,b[100010];
int lowbit(int x){ return x&(-x); }
int ask(int x){//求a[x]
int ans=0;
while(x){ ans+=b[x],x-=lowbit(x); }
return ans&1;
}
void add(int x,int k){
while(x<=n){ b[x]+=k,x+=lowbit(x); }
}
int main(){
cin>>n>>m;
for(int i=0;i<m;i++){
cin>>op;
if(op==1){
cin>>x>>y;
add(x,1),add(y+1,-1);
}
else{
cin>>x;
cout<<ask(x)<<endl;
}
}
return 0;
}
P2068 统计和
注意可能会爆int
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m,x,y,b[100010];
char op;
ll lowbit(ll x){ return x&-x; }
void add(ll x,ll k){
while(x<=n){ b[x]+=k,x+=lowbit(x); }
}
ll ask(ll x){
ll ans=0;
while(x){ ans+=b[x],x-=lowbit(x); }
return ans;
}
int main(){
cin>>n>>m;
while(m--){
getchar();//读取行末的回车
cin>>op>>x>>y;
if(op=='x') add(x,y);
else cout<<ask(y)-ask(x-1)<<endl;
}
return 0;
}
P4939 Agent2
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,op,b[10000010];
int lowbit(int x){ return x&-x; }
void add(int x,int k){
while(x<=n){ b[x]+=k,x+=lowbit(x); }
}
int ask(int x){
int ans=0;
while(x){ ans+=b[x],x-=lowbit(x); }
return ans;
}
int main(){
cin>>n>>m;
while(m--){
cin>>op>>x;
if(!op){
cin>>y;
add(x,1);
add(y+1,-1);
}
else cout<<ask(x)<<endl;
}
return 0;
}
P5094 [USACO04OPEN] MooFest G 加强版
题目所求即
首先将牛按听力大小排序,遍历n头牛,每头牛计算与前面的牛的音量(即处理了max)
下面处理|j-i|,考虑第i头牛
用两个树状数组分别维护前i-1头牛中坐标小于a[i].x的个数cnt;
以及前i-1头牛中坐标小于a[i].x的牛的坐标和sum;
计算前i-1头牛的坐标总的tot
随后就可以求出音量和为(tot-2*sum+(2*cnt-i+1)*a[i].x)*a[i].v
!!!注意add里面x<=50000不是小于等于n
调了一万年!!!!!! 注意开ll !!!
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a1[50010],a2[50010],n,sum,cnt,ans,tot;
struct cow{ ll v,x; }a[50010];
bool cmp(cow c1,cow c2){ return c1.v<c2.v; }
ll lowbit(ll x){ return x&-x; }
ll ask(ll* ar,ll x){
ll ans=0;
while(x){ ans+=ar[x]; x-=lowbit(x); };
return ans;
}
void add(ll* ar,ll x,ll k){
while(x<=50000){ ar[x]+=k; x+=lowbit(x); };
}
int main(){
//a1储存前i-1头牛中坐标小于等于a[i].x的牛的坐标和
//a2储存前i-1头牛中坐标小于等于a[i].x的牛的个数
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].v>>a[i].x;
sort(a+1,a+n+1,cmp);
add(a1,a[1].x,a[1].x);
add(a2,a[1].x,1);
tot+=a[1].x;
for(int i=2;i<=n;i++){
sum=ask(a1,a[i].x);
cnt=ask(a2,a[i].x);
ans+=(tot-2*sum+(2*cnt-i+1)*a[i].x)*a[i].v;
add(a1,a[i].x,a[i].x);
add(a2,a[i].x,1);
tot+=a[i].x;
}
cout<<ans;
return 0;
}