你是否讨厌线段树那冗长的代码?你是否还在因为线段树的难调试而满头♂dark汗?那么,请不要错过!超级树状数组特价!只要998,只要998!
##¥……#……¥%……&%¥……ER#%$#$#^T%$^$%
超级树状数组,其实是一种能够支持区间修改和区间查询的树状数组,和线段树相比,它的常数极小,不需要太多空间,代码量也少了很多(简直吊打线段树)
1.树状数组
既然是超级树状数组,那么就需要一个树状数组作为基础了。但是在真正实现时,只用到了lowbit()函数(所以说lowbit是树状数组的核心啊)
2.准备工作
首先,我们需要一个差分数组。
设a[]数组为原数组,那么tree[](差分数组)定义为tree[i]=a[i]-a[i-1]
猴子也能一眼看出的性质:a[i]=tree[1]+tree[2]+tree[3]+...+tree[i]
3.区间查询
(为什么先说查询呢。。)
(1)查询区间1.....l的和
sum[l]=a[1]+a[2]+...+a[l]
其中a[i]=tree[1]+...+tree[i]
那么我们可以很那啥的得到这个式子
t1+t1+t2+t1+t2+t3+....+t1+t2+t3+....+tl(这啥玩意啊)
如果你用数学角度去看的话,它是下面这个样子
t1*l+t2*(l-1)+t3*(l-2)+....+tl*1
如果你旁边坐着一位数竞大佬,ta会立刻看成这个样子
l*(t1+t2+....+tl)-(t1*0+t2*1+...+tl*(l-1))
然后我们惊奇的发现,这两个部分都是可以维护的
所以我们就可以在输入时处理出一个差分数组和一个tree1[i]=tree[i]*(i-1)
然后就可以查询了
(2)查询l.....r的和
类比前缀和处理
(3)代码
long long getsum(long long *arr,long long pos){ long long sum=0; while(pos) sum+=arr[pos],pos-=lowbit(pos); return sum; } long long query(long long x,long long y){ return y*getsum(d1,y)-(x-1)*getsum(d1,x-1)-(getsum(d2,y)-getsum(d2,x-1)); }
4.区间修改
类比树状数组的区间修改
void add(long long *arr,long long pos,long long x){ while(pos<=n) arr[pos]+=x,pos+=lowbit(pos); }
但是,由于tree和tree1的存在,修改需要改一下
void change(long long l,long long r,long long x){ add(d1,l,x); add(d1,r+1,-x); add(d2,l,x*(l-1)); add(d2,r+1,-x*r); }
若是将区间l-r加上x,就可以tree[l]+x,tree[r]-x,这样保证在计算a[i]时能让l-r内的数+x而其他不+x
放代码
#include<cstdio> #include<algorithm> //long long tree[100001]; long long n,m; long long d1[100001]; long long d2[100001]; inline long long lowbit(long long x) { return x&-x; } /*void add(long long x,long long k)//μ¥μ?DT?? { while(x<=n){ tree[x]+=k; x+=lowbit(x); } } long long sum(long long pos) {//????2é?ˉ long long sum=0; while(pos){ sum+=tree[pos]; pos-=lowbit(pos); return sum; } } void add_ex(long long pos,long long x) {//????DT?? while(pos<=n){ detla[pos]+=x; pos+=lowbit(pos); } } void sum_ex(long long l,long long r,long long x) { add_ex(l,x); add(r+1,-x); } long long sum_ex(long long pos)//μ¥μ?2é?ˉ { long long sum=0; while(pos){ sum+=detla[pos]; pos-=lowbit(pos); } return sum; }*/ //ò???ê?????DT??+????2é?ˉ void add(long long *arr,long long pos,long long x){ while(pos<=n) arr[pos]+=x,pos+=lowbit(pos); } void change(long long l,long long r,long long x){ add(d1,l,x); add(d1,r+1,-x); add(d2,l,x*(l-1)); add(d2,r+1,-x*r); } long long getsum(long long *arr,long long pos){ long long sum=0; while(pos) sum+=arr[pos],pos-=lowbit(pos); return sum; } long long query(long long x,long long y){ return y*getsum(d1,y)-(x-1)*getsum(d1,x-1)-(getsum(d2,y)-getsum(d2,x-1)); } //ò???ê?×??μ /* void build(long long n){ for(long long i=1;i<=n;i++){ tree[i]=a[i]; long long t=lowbit(i); for(long long j=1;j<t;j*=2) tree[i]=std::max(tree[i],tree[i-j]); } } void add(long long pos,long long x){ a[pos]=x; while(pos<=n){ tree[pos]=a[pos]; long long t=lowbit(i); for(long long j=1;j<t;j++){ tree[i]=std::max(tree[i],tree[i-j]); } pos+=lowbit(pos); } } long long query(long long l,long long r){ long long ans=a[r]; while(1){ ans=std::max(ans,tree[r]); if(r==l)break;r--; while(r-l>=lowbit(r))ans=std::max(ans,tree[r]),r-=lowbit(r); } return ans; } */ int main()//ê÷×′êy×é′ó?£°? { scanf("%lld%lld",&n,&m); long long a,b=0; for(long long i=1;i<=n;i++){ scanf("%lld",&a); b=a-b; add(d1,i,b); add(d2,i,(i-1)*b); b=a; } while(m--){ long long op; scanf("%lld",&op); if(op==1){//???μ long long x,y,z; scanf("%lld%lld%lld",&x,&y,&z); change(x,y,z); }else{ long long x,y;//2é?ˉ scanf("%lld%lld",&x,&y); printf("%lld ",query(x,y)); } } }
5.吊打线段树
现在让我们统计一下超级树状数组的核心代码长度
17行。。。。~~线段树你可以去死了~~
让我们看一下超级树状数组和线段树在跑模板时的时间与空间
ok线段树你真的可以当场去世了~