树状数组比较适合于对于给定数组求前缀和(区间和),但是只能单点更新,不容易做区间更新,但是今天学会了可以利用差分的方法来做区间更新,可以很快实现单点查询;
上模板~
首先是lowbit:
inline int lowbit(int k){return (k&(-k));}
然后是单点更新:
inline void add(int x,long long k){
for(;x<=n;x+=lowbit(x)) a[x]+=k;
}
然后是求前缀和:
inline long long sum(int x){
int sum=0;
for(;x>0;x-=lowbit(x)) sum+=a[x];
return sum;
}
模板结束;
现在看一下如何进行区间更新和单点查询:
先上一段kanate_saikou大神的题解:
这模板题名字就是树状数组..干嘛用线段树..代码还贼长..
这里介绍树状数组+差分思想,算是对下面大神的补充吧。
何为差分
现在我们有一个从小到大的数列a[]
a 1 3 6 8 9
然后还有一个差分数组b[]
b 1 2 3 2 1
相信某些小伙伴已经看出端倪了..这里b[i]=a[i]-a[i-1],我令a[0]=0,故b[1]=a[1]。
拥有了b数组,我们就可以很简单的求出bit[]中任意一个数,只需bit[i]=sigma(k=1 to i) b[k](这个很好推吧..)
我觉得现在该有人说我zz了..何必不直接查询a[i]而是找这么麻烦一个方法..这里我们转回正题!别忘了,题目要我们进行区间修改..
我们知道,树状数组对于单点值的修改十分方便(不懂的去看树状数组1),对于区间的修改就比较尴尬..而我们又不想敲死长的线段树..怎么办呢,这时候差分就显出优势
还是上面的a[]和b[],现在我们使区间[2,4]的所有数均+2,则a[]/b[]变为
a 1 5 8 10 9
b 1 4 3 2 -1
事实上,这里只有b[2]和b[5]发生了变化,因为区间内元素均增加了同一个值,所以b[3],b[4]是不会变化的。
这里我们就有了第二个式子:对于区间[x,y]的修改(增加值为d)在b数组内引起变化的只有 b[x]+=d,b[y+1]-=d。(这个也很好推的..)
这样,我们就把树状数组的软肋用差分解决了。
所以可以用差分的方法来进行区间更新;
可以发现,我们其实只需要维护 delta,即b数组,对于原数组可以不用保存,因为对于任意的a[i]值,我们都可以通过a[x]=sigma[i=1 to x](b[i])来算出
就是a[i]=sigma(k=1 to i) b[k];
在读入的时候,我们可以直接把a[i]-a[i-1]读入数组,在
主函数如下:
int main() {
std::ios::sync_with_stdio(false);
cin>>n>>m;
int com,x,y=0;
ll k;
for(int i=1;i<=n;i++){
cin>>x;
add(i,x-y);
y=x;
}
while(m--){
cin>>com;
if(com==1){
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
if(com==2){
cin>>x;
cout<<sum(x)<<endl;
}
}
return 0;
}
在读入的时候,我们只需要add(i,x-y);
在进行单点更新的时候,我们只需要add(x,k);add(y+1,-k);
最后我们求出的sum其实就是a[i]的值;
真巧妙 hohoho~
最后附上一段对树状数组的理解【果然写的多了就理解了hhh】:
先附上一段copy的大神博客里的理解;
然后我简单聊一下我对树状数组的理解;
你看啊;
比如我们要求14的前缀和,我们是不是只要加上14 12 8 所在的柱就好了;
我们分析分析;
14-1110
12-1100
08-1000
我们是不是加上14后删掉14的最后一个1,即lowbit就好啦;
在来一组
16-10000
08-1000
06-0110
我们结合图可以知道
8的前缀 包含了6的值;
16的前缀包含了8的值,也包含了6的值;
如果6的值修改了;8的前缀和和16前缀和是不是也要改;
所以我们要把8和16也改掉;
所以我们加lowbit;
对吧
所以我们可以看到,如果我们想要求一个点x的前缀和,我们只需要把所有与x前缀和相关的点的值加起来,这些相关的值,就存储在x-lowbit(x)里面,而且不停地减下去,直到0为止;比如,我想求23的前缀和,23的二进制表示是10111,去掉最后一位的1,得到了10110,就是22,同时,23的lowbit就是1,23-1=22;那么得到了22,22的二进制表示是10110,去掉最后一位的1,得到了10100,就是20,同时,22的lowbit就是2,22-2=20;那么得到了20,20的二进制表示是10100,去掉最后一位的1,得到了10000,就是16,同时,20的lowbit就是4,20-4=16;那么得到了16,16的二进制表示是10000,去掉最后一位的1,得到了00000,就是0,同时,16的lowbit就是16,16-16=0;那么此时就是0。
通过上面这一段计算,我们得到了,23->22->20->16->0,得到了这几个数字,实际上,我们所要求的sum(23)=c[23]+c[22]+c[20]+c[16]+c[0];
另一方面,如果我们想去改变点6的值,那么我们需要向上改变哪些点的值呢?
首先,对于6这个值,我们求出他的lowbit(6)=2,然后我们需要更新6+2=8这个点;然后lowbit(8)=8,所以我们要去更新8+8=16这个点;然后lowbit(16)=16,然后我们要去更新16+16=32这个点;然后依次往上~直到碰到上限n为止;
上述就是sum和add两个操作的理解;
最后
OMG