说起树状数组,那么首先要掌握差分和前缀和这两个概念(大部分题就相关)
前言
差分和前缀和的概念是相反的,原数组是差分数组的前缀和
也就是原数组a[],差分数组d[],满足d[i]=a[i]-a[i-1];
a[i]=d[1]+....+d[i]
前缀和相反
树状数组
树状数组我感觉就是对差分数组的一种二进制压缩,原本来说差分数组查询原数组的时间复杂度为n,经过差分数组的压缩变为log2n
他是利用二叉树的样式来存储
切入点:lowbit函数
由于电脑一种叫做补码的操作(由于电脑是二进制,它们存的相反数是它的取反+1),一个数与它的相反数做与操作时会返回二进制下最右边的1的位置。举个例子: 6&-6=2 将6变成二进制:110。其中最右侧的1加粗字体:110则返回的是二进制下10的值:2。得到代码:
arduino 代码解读复制代码public static int lowbit(int x){
return x&-x;
}
利用这个性质建立树状数组:
为了简化区间修改的效率,我们需要建立这样的一个数组:数组中第k位的值为原数组中的一段区间和,这个区间的长度是lowbit(k),终点是k。 比如:输入一个数组,那么我们所建立的数组:
- 第一位(1在二进制下=1 二进制下的1=1)的值为输入的数组的第一位往前的一位的和,也就是第一位。
- 第二位(2在二进制下=10 二进制下的10=2)的值为输入的数组的第二位往前两位的和,第一位和第二位。
- 第三位(3在二进制下=11 二进制下的1=1)的值为输入的数组的第三位往前一位的和,也就是第三位。
- 第四位的值(4在二进制下=100 二进制下的100=4)的值为输入的数组的第四位往前四位的和,也就是第一位,第二位,第三位以及第四位。
我们就称这种数组为树状数组
scss 代码解读复制代码static void update(int x,int val){ //x:修改的位置 val: 增加的值
while (x<=N){
tree[x]+=val;
x+=lowbit(x);
}
}
这样就很好传递了,有点动归的意思哈
相当于7二进制位111,利用lowbit查询到最后一个1的位置为1减去为110(6),重复得到100(4),重复得到000(舍去)这样sum[7]=tree[6]+tree[4]+tree[7],这样修改后的原数组查询就大大减少时间复杂度了
ini 代码解读复制代码static int sum(int x){
int ans=0;
while (x!=0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
修改区间:
根据题意去设定tree存储的是前缀和还是差分
1.单点修改+区间和查询使用前缀和的思想
ini 代码解读复制代码for (int i = 1; i <= n; i++){
int x=sc.nextInt();
//初始化数据
update(i,x);
}
for (int i = 1; i <= m; i++){
int op=sc.nextInt();
if(op==1){
//修改第a,+k
int a=sc.nextInt();
int k=sc.nextInt();
update(a,k);
}
if(op==2){
//查询区间{a,b}
int a=sc.nextInt();
int b=sc.nextInt();
System.out.println(sum(b)-sum(a-1));
}
2.区间修改+单点查询使用差分的思想
ini 代码解读复制代码 for (int i = 1; i <= n; i++){
int x=sc.nextInt();
//初始化数据
update(i,x);
update(i+1,-x);
}
for (int i = 1; i <= m; i++){
int op=sc.nextInt();
if(op==1){
//修改[a,b]区间内+k
int a=sc.nextInt();
int b=sc.nextInt();
int k=sc.nextInt();
update(a,k);
update(b+1,-k);
}
if(op==2){
//查询
int a=sc.nextInt();
System.out.println((long) sum(a));
}
}
这两个的区别就在于初始化tree数组的方法
3.区间修改+区间查询
可以使用线段树的方式去解答
这里采用差分的思想去解答(因为差分可以对原数组进行修改查询)
但是区间和和差分相差两段累加和的结果.但是我们不难发现,区间和和差分数组还是有关系的
tree[1]+tree[2]+……tree[i]=a[i]
那么对于区间[l,r]
a[1]+a[2]+……+a[r-1]+a[r] //用上方公式推导得出
=tree[1]+(tree[1]+tree[2])+……+(tree[1]+……+tree[r]) //根据加法交换律与结合律:
=(tree[1](r))+(tree[2](r-1))+……(tree[r]*1) //那么:
=r*(tree[1]+tree[2]+……+tree[r])-(tree[1]*0+tree[2]1+……+tree[r](r-1))
这样我们一样可以通过sum来得到区间和
单独用tree1[i]=tree[i]*(i-1)存储,然后计算tree1的前缀和就行了
代码:
ini 代码解读复制代码for (int i = 1; i <= n; i++) {
long x=sc.nextInt();
update(i,x-old,tree1);
update(i,(i-1)*(x-old),tree2);
old=x;
}
while (m-->0){
int op=sc.nextInt();
if(op==1){
long l=sc.nextInt();
long r=sc.nextInt();
long k=sc.nextInt();
update(l,k,tree1);
update(r+1,-k,tree1);
update(l,(l-1)*k,tree2);
update(r+1,-r*k,tree2);
}else {
long l=sc.nextInt();
long r=sc.nextInt();
System.out.println(r*sum(r,tree1)-sum(r,tree2)-(l-1)*sum(l-1,tree1)+sum(l-1,tree2));
}
}
评论记录:
回复评论: