【树状数组模板】 洛谷P3368

树状数组比较适合于对于给定数组求前缀和(区间和),但是只能单点更新,不容易做区间更新,但是今天学会了可以利用差分的方法来做区间更新,可以很快实现单点查询;

上模板~

首先是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

猜你喜欢

转载自blog.csdn.net/qq_33982232/article/details/81273634