树状数组 区间修改,单点查询;
https://www.luogu.org/problem/show?pid=3368#sub
线段树水题啊;
但是我们要学习树状数组;
树状数组水题啊;
首先假如我们会模版1;
其实我们发现,直接区间修改会产生一些遗漏
add(x,z);
add(y+1,-z);
这样的话,说不定x+1~y没有加z;
这个就不好了;
比如1 2 3 4 5
x=2;y=4;z=1;
如果直接输出答案;
outit(x)-outit(x-1);
这样的话,3是不会加一的啊;
那怎么办呢
我复制一下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。(这个也很好推的..)
这样,我们就把树状数组的软肋用差分解决了。
代码:
#include<cstdio>//cfb
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
int a[500001];
int n,m,x,y,z;
void add(int x,int y){
for(int i=x;i<=n;i+=i&-i)a[i]+=y;
}
int outit(int x){
int sum=0;for(int i=x;i;i-=i&-i)sum+=a[i];return sum;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&x),add(i,x-y),y=x;
while(m--){
scanf("%d%d",&z,&x);
if(z==1){
scanf("%d%d",&y,&z);
add(x,z);
add(y+1,-z);
}else printf("%d\n",outit(x));
}
}
然后我简单聊一下我对树状数组的理解;
你看啊;
比如我们要求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;
对吧