区间操作---树状数组&&线段树
涉及区间操作的一些套路必须要会呀
区间加减为了偷懒能不写线段树so我选择树状数组!!
但是区间乘除,最大值我想了想还是用线段树分块吧。
树状数组:
这里用网上的一张图:
这里灰色数组是原本的数组(a[i])红色数组则是树状数组(c[i])这里直接给出结论:
c[i]=a[i-2^k+range[1,2^k]]
k是i的二进制位从低到高位连续0的个数
与a[i]有关的 c[i+2^(k+j)] 且 i+2^(j+k)<n
这样就很好实现单点更改,区间查询了。
void lowbit(int x) { return x&(-x);//求2^x } void updata(int x,int val)//在x处加val { while(x<=n) { c[i]+=val; x+=lowbit(x);//x在不断变化要不断求lowbit } } int getsum(x)//求a[1~x]的和 { int ans=0; while(x) { ans+=c[x]; x-=lowbit(x); } return ans; }
区间查询怎么搞呢?
这里可以用到差分数组
比如在a=[1,5,7,2,3,7,1]则有的差分数组d=[1,4,2,-5,1,4,-6](a[i]-a[i-1)a[0]=0,sum[d[1~i]]=a[i]
在区间x,y全部加k则可以在d[x]+k,d[y+1]-k 则在求1~i i in range[x,y]这部分区间的和即可得到a[i]+k。这样就变成了单点修改区间查询了
这样实际上a数组是没有用的,’a‘则变成了d数组,对d数组构造树状数组c
这样就实现了区间修改,单点查询:
单点查询模板 https://www.luogu.com.cn/problem/P3368
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int N=5e5+6; int n,m,q,p,w,x,ans,a[N],c[N]; int lowbit(int x) { return x&(-x); } void update(int x,int val)//x点+val值 { while(x<=n) { c[x]+=val; x+=lowbit(x);//x在不断变化要不断求lowbit } } int getsum(int x)//求'a'[1~x]的和 { int res=0; while(x) { res+=c[x]; x-=lowbit(x); } return res; } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); update(i,a[i]-a[i-1]);//建立差分数组d } for(int i=1;i<=m;i++) { scanf("%d",&x); if(x==1) { scanf("%d %d %d",&q,&p,&w);//q,p区间+w update(q,w); update(p+1,-w); } else { scanf("%d",&q); ans=getsum(q); //求d的1~q的和即为a[q] printf("%d\n",ans); } } return 0; }
区间修改怎么搞呢?
sum[a[1~n]]=d[1]+d[1~2]+...+d[1~n]=n*d[1]+(n-1)d[2]+...+d[n]=n*d[1~n]-(0*d[1]+1*d[2]+...+(n-1)*d[n])(在树状数组里就是另一种表示,这里只是普通数组表示)
可见两部分变量可写成=n*sum1[n]-sum2[n](又是区间求和了)
永远要记住求谁的和构建谁的树状数组,然后再树状数组求和,因为优化是树状数组求和造成的
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int N=5e5+6; int n,m,q,p,w,x,ans,a[N],c1[N],c2[N]; int lowbit(int x) { return x&(-x); } void update(int x,int val)//x点+val值 { int i=x; while(i<=n) { c1[i]+=val;//c1就是d数组的树状数组 c2[i]+=(x-1)*val; //c2是d[i]*(i-1)的树状数组 i+=lowbit(i);//i要不断变化所以要不断求lowbit } } int getsum(int x)//求a[1~x]的和 { int res=0,res1=0,res2=0,i=x; while(i) { res1+=c1[i]*x;//求d[1~x](sum1)根据分配律可直接求得sum1*n res2+=c2[i];//求sum2 //res+=c[i]*x-c2[i];就是单纯的求a[1~x] i-=lowbit(i); } res=res1-res2;//a[1~x] return res; } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); update(i,a[i]-a[i-1]);//建立 } for(int i=1;i<=m;i++) { scanf("%d",&x); if(x==1) { scanf("%d %d %d",&q,&p,&w);//q,p区间+w update(q,w); update(p+1,-w); } else { scanf("%d",&q); ans=getsum(q)-getsum(q-1); printf("%d\n",ans); } } return 0; }
线段树整合:
忘不了的线段树开四倍
区间加减,乘除,最大值:
代码还是过那个模板题的
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int N=5e5+6; int n,m,q,p,w,x,ans,a[N]; struct node { int sum,add,mul;//add,mul是lazy_tag }t[N*4]; void push_down(int p) { t[p].sum=t[p*2].sum+t[p*2+1].sum;//%mod return ; } void built(int p,int l,int r) { t[p].add=0; t[p].mul=1; if(l==r) { t[p].sum=a[l]; //mod; return ; } int mid=(l+r)/2; built(p*2,l,mid); built(p*2+1,mid+1,r); push_down(p); return ; } int askmin(int p,int qx,int zx,int gl,int gr) { if(qx>=gl&&zx<=gl) { return t[p].minn; } int mid=(qx+zx)/2,ans=9999999; if(gl<=mid) ans=min(ans,askmin(p*2,qx,mid,gl,gr)); if(gr>mid) ans=min(ans,askmin(p*2+1,mid+1,zx,gl,gr)); return ans; } void push_tag(int p,int l,int r) { if(t[p].mul!=1)//必须先更新乘法 { t[p*2].sum*=t[p].mul;//mod t[p*2+1].sum*=t[p].mul; t[p*2].add*=t[p].mul;//mod t[p*2+1].add*=t[p].mul; t[p*2].mul*=t[p].mul;//mod t[p*2+1].mul*=t[p].mul; t[p].mul=1; } if(t[p].add)//加不会对乘的tag产生影响 { int mid=(l+r)/2; t[p*2].add+=t[p].add;//mod t[p*2+1].add+=t[p].add; t[p*2].sum+=(mid-l+1)*t[p].add;//mod t[p*2+1].sum+=(r-mid)*t[p].add; t[p].add=0; } return ; } void add(int p,int qx,int zx,int gl,int gr,int k)//区间加减 { if(qx>=gl&&zx<=gr) { t[p].sum+=(zx-qx+1)*k; t[p].add+=k;//mod return ; } int mid=(qx+zx)/2; push_tag(p,qx,zx); if(gl<=mid) add(p*2,qx,mid,gl,gr,k); if(gr>mid) add(p*2+1,mid+1,zx,gl,gr,k); push_down(p); return ; } void mult(int p,int qx,int zx,int gl,int gr,int k)//区间乘 { if(qx>=gl&&zx<=gr) { t[p].sum*=k; t[p].add*=k; t[p].mul*=k;//mod return ; } int mid=(qx+zx)/2; if(gl<=mid) mult(p*2,qx,mid,gl,gr,k); if(gr>mid) mult(p*2+1,mid+1,zx,gl,gr,k); } int ask(int p,int qx,int zx,int gl,int gr) { if(qx>=gl&&zx<=gr) return t[p].sum;//mod int ans=0,mid=(qx+zx)/2; push_tag(p,qx,zx); if(gl<=mid) ans+=ask(p*2,qx,mid,gl,gr); if(gr>mid) ans+=ask(p*2+1,mid+1,zx,gl,gr); return ans; } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); built(1,1,n); for(int i=1;i<=m;i++) { scanf("%d",&x); if(x==1) { scanf("%d %d %d",&q,&p,&w);//q,p区间+w add(1,1,n,q,p,w); } if(x==2) { scanf("%d",&q); printf("%d\n",ask(1,1,n,q,q)); } } return 0; }