以前的博客提到过自己没怎么写过线段树。。现在要开始补起来了。
AC代码如下(一开始没看见负数读入优化写渣WA了几发。。真是无语):
<span style="font-size:18px;">#include<iostream> #include<cstdio> #include<cstring> #define N 500005 #define ll long long int n,m,icr[N],c[N][2]; ll sum[N]; int read(){ int x=0,tmp=1; char ch=getchar(); while (ch!='-' && (ch<'0' || ch>'9')) ch=getchar(); if (ch=='-'){ tmp=-1; ch=getchar(); } while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x*tmp; } void pushdown(int k){ if (icr[k]){ int l=c[k][0],r=c[k][1],mid=(l+r)>>1; icr[k<<1]+=icr[k]; icr[k<<1|1]+=icr[k]; sum[k<<1]+=(ll)icr[k]*(mid-l+1); sum[k<<1|1]+=(ll)icr[k]*(r-mid); icr[k]=0; } } void build(int k,int x,int y){ c[k][0]=x; c[k][1]=y; icr[k]=0; if (x==y){ sum[k]=read(); return; } else{ int mid=(x+y)>>1; build(k<<1,x,mid); build(k<<1|1,mid+1,y); sum[k]=sum[k<<1]+sum[k<<1|1]; } } void ins(int k,int x,int y,int v){ int l=c[k][0],r=c[k][1],mid=(l+r)>>1; if (x==l && y==r){ icr[k]+=v; sum[k]+=(ll)v*(r-l+1); return; } pushdown(k); if (y<=mid) ins(k<<1,x,y,v); else if (x>mid) ins(k<<1|1,x,y,v); else{ ins(k<<1,x,mid,v); ins(k<<1|1,mid+1,y,v); } sum[k]=sum[k<<1]+sum[k<<1|1]; } ll qry(int k,int x,int y){ int l=c[k][0],r=c[k][1],mid=(l+r)>>1; if (x==l && y==r) return sum[k]; pushdown(k); if (y<=mid) return qry(k<<1,x,y); else if (x>mid) return qry(k<<1|1,x,y); else{ ll tmp=qry(k<<1,x,mid); tmp+=qry(k<<1|1,mid+1,y); return tmp; } } int main(){ while (~scanf("%d%d",&n,&m)){ build(1,1,n); int i; char ch; for (i=1; i<=m; i++){ ch=getchar(); while (ch!='Q' && ch!='C') ch=getchar(); if (ch=='C'){ int x=read(),y=read(),v=read(); ins(1,x,y,v); } else{ int x=read(),y=read(); printf("%I64d\n",qry(1,x,y)); } } } return 0; }</span>
2016.1.8
-----------------------------------------------------------------------------------------------
后来发现还有树状数组写法,补了一发(以后肯定写树状数组了才1k代码)。
我们知道,区间修改单点查询可以用树状数组完成。假设原来的数组是a,利用一个辅助数组,比如说b,然后将a[i]转化为b[1]+b[2]+...+b[i],即a数组相当于b数组的前缀和,即将单点转化为前缀和,那么区间修改(x,y)都加上z只要b[x]+=z,b[y+1]-=z,简单推理就发现仍然满足a[i]=b[1]+b[2]+...+b[i]。于是就转化为了单点修改维护前缀和。如果是要区间查询,我们就相当于要维护前缀和的前缀和,这在bzoj上有这么一道题,具体那一道忘记了。
实际上,令c[i]=a[1]+a[2]+...a[i]=b[1]*i+b[2]*(i-1)+...+b[i]*1=b[1]*n+b[2]*(n-1)+...+b[i]*(n-i+1)-(b[1]+b[2]+...+b[n])*(n-i),换句话说我们只要维护一个b数组的前缀和,再维护一个形如b[1]*n,b[2]*(n-1),...,b[n]*1的一个数组的前缀和就可以得到双重前缀和了。利用树状数组即可。
AC代码如下:
<span style="font-size:18px;">#include<iostream> #include<cstdio> #include<cstring> #define ll long long #define N 100005 using namespace std; int n,m; ll c[2][N]; int read(){ int x=0,tmp=1; char ch=getchar(); while (ch!='-' && (ch<'0' || ch>'9')) ch=getchar(); if (ch=='-'){ tmp=-1; ch=getchar(); } while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x*tmp; } void ins(int k,int x,ll t){ int i; for (i=x; i<=n; i+=i&(-i)) c[k][i]+=t; } ll qry(int k,int x){ ll sum=0,i; for (i=x; i; i-=i&(-i)) sum+=c[k][i]; return sum; } void mdy(int x,int y,ll t){ ins(0,x,t); ins(0,y+1,-t); ins(1,x,t*(n-x+1)); ins(1,y+1,-t*(n-y)); } int main(){ while (~scanf("%d%d",&n,&m)){ int i; char ch; memset(c,0,sizeof(c)); for (i=1; i<=n; i++){ int tmp=read(); mdy(i,i,(ll)tmp); } while (m--){ ch=getchar(); while (ch!='Q' && ch!='C') ch=getchar(); if (ch=='C'){ int x=read(),y=read(),z=read(); mdy(x,y,(ll)z); } else{ int x=read()-1,y=read(); printf("%I64d\n",qry(1,y)-qry(0,y)*(n-y)-qry(1,x)+qry(0,x)*(n-x)); } } } return 0; } </span>
2016.1.9