防止自己刚上完课就忘了
先看这玩意,怎么写?
1,写一个简单的一维数组,对于第一个操作直接a[i]+=x,对于第二个暴力便利,然后喜提30分
2,树状数组
3,线段树
现在我们先讲第二个
对于一个数组如:a[]={2,1,4,3,7,9,-1,6},如何求出其所有元素的和呢
反正这样可知道和为31
如果数组的元素有九个就是这样
发现没,很像一棵树(你当他像就完了)
假如我们设这些数字的索引为1-8,则可以用logn的时间复杂度解出每一个元素前几项的和(包括自己)
索引为8的元素(6)前面的总和就是31
索引为7的元素(-1)前面的就是总和10+16+(-1)=25
索引为5的元素(7)前面的就是总和10+7=17
如果只考虑加法运算的话,我们发现只有不标红的元素有用(是不是很神奇)
大家可以举出几个例子,至于原理是什么,就类似于二进制只有1和0(0代表没,1代表有),如果出现了2直接表示为大一位的一就好了,理解不了就别理解(二进制的(10)和十进制的(2))
发现没,不标红的元素个数正好等于原数组的元素个数
所以对于树状数组,直接开和原数组长度相等的空间,往里头填就好了
那怎么求解呢(现在的树状数组为2,3,4,10,7,16,-1,31)
假如我们要求索引为6的元素(9)以前(包括自己)的元素和,那和为16+0,即树状数组索引为6的元素和索引为4的元素相加(二进制的6为110,二进制的4为100)
假如我们要求索引为3的元素(4)以前(包括自己)的元素和(二进制的3为11),那和为3+4,即树状数组索引为3的元素和索引为1的元素相加(二进制的3为11,二进制的2为10)
假如我们要求索引为7的元素(-1)以前(包括自己)的元素和(二进制的7为111),那和为10+16+(-1),即树状数组索引为7的元素和索引为6的元素和和索引为4的元素相加(二进制的7为111,二进制的6为110,二进制的4为100)
发现没,只要每次将要求的索引的二进制抹去末尾的1(111->110->100),再将这些处理过的索引对应的元素加起来,所得值就是答案
那我要怎么样抹去末尾的1呢,十进制转二进制再遍历删除再转二进制吗?
nonono
当当当当lowbit
int lowbit(int x)
{
return x&-x;
}
这是啥?
原理自己查(我也不知道,类似于100-99?)反正就是能返回x所对应的二进制数只保留从后往前第一个1的结果
#include <bits/stdc++.h>
using namespace std;
int lowbit(int x)
{
return x&-x;
}
int main()
{
cout<<lowbit(4)<<' '<<lowbit(3)<<' '<<lowbit(9)<<' '<<lowbit(8);
}
这个程序输出为:4 1 1 8
因为4,3,9,8这四个数的二进制表达分别为100,11,1001,1000,只保留末尾第一个1的二进制是100,1,1,1000,即十进制的4,1,1,8
那就可以简单的判断要加上那些数字了,让索引每次减去lowbit(索引)就好了(注意别减到0)
int t[MAXN]//用来存树状数组
int getsum(int x)
{
int sum=0;
for(int i=x;i!=0;i-=lowbit(i))
{
sum+=t[i];
}
return sum;
}
要求一段区间的总和值就是getsum(a)-getsum(b)
那怎么写修改值呢
我们注意到,假如我们现在想是索引为3的元素的值+2
只需要让标红的元素都+2就好啦
具体怎么实现呢,只需要每次将索引+lowbit(索引)就好了
void pointadd(int idx,int x)//使索引为idx的元素加上x
{
for(int i=idx;i<=n;i+=lowbit(i))
{
t[i]+=x;
}
}
有人要问了,怎么初始化呢
很简单,直接把数组初始化为0,每次都加就好了
把所有的要素,全部加起来
#include <bits/stdc++.h>
#define MAXN (int)5e5+7
using namespace std;
int a[MAXN],t[MAXN],n,m;
int lowbit(int x)
{
return x&-x;
}
int getsum(int x)
{
int sum=0;
for(int i=x;i;i-=lowbit(i))
{
sum+=t[i];
}
return sum;
}
void pointadd(int idx,int x)
{
for(int i=idx;i<=n;i+=lowbit(i))
{
t[i]+=x;
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
pointadd(i,a[i]);
}
int op,x,k;
for(int i=1;i<=m;i++)
{
cin>>op>>x>>k;
if(op==2)
{
cout<<getsum(k)-getsum(x-1)<<endl;
}
else
{
pointadd(x,k);
}
}
return EXIT_SUCCESS;
}
就可以做出来了
课下作业:
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
提示:查分与树状数组结合