题目
需要满足两种操作,区间修改和区间查询
分析(树状数组)
设tree[i]=a[i]−a[i−1]tree[i]=a[i]-a[i-1]tree[i]=a[i]−a[i−1](差分),那么容易得到:
tree[1]+tree[2]+…+tree[i]=a[i]tree[1]+tree[2]+…+tree[i]=a[i]tree[1]+tree[2]+…+tree[i]=a[i]这个公式
所以,只需要维护treetreetree数组就可以实现区间修改了。
如何实现区间查询呢?
我们已经推出了一个公式:
tree[1]+tree[2]+…tree[i]=a[i]tree[1]+tree[2]+…tree[i]=a[i]tree[1]+tree[2]+…tree[i]=a[i]
那么,对于1到r的区间和,即为:
a[1]+a[2]+……+a[r−1]+a[r]a[1]+a[2]+……+a[r-1]+a[r]a[1]+a[2]+……+a[r−1]+a[r]
=tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r])=tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r])=tree[1]+(tree[1]+tree[2])+…+(tree[1]+…+tree[r])
=(tree[1]∗r)+(tree[2]∗r−1)+…(tree[r]∗1)=(tree[1]*r)+(tree[2]*r-1)+…(tree[r]*1)=(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))=r*(tree[1]+tree[2]+…+tree[r])-(tree[1]*0+tree[2]*1+…+tree[r]*(r-1))=r∗(tree[1]+tree[2]+…+tree[r])−(tree[1]∗0+tree[2]∗1+…+tree[r]∗(r−1))
对于aaa的树状数组(差分)treetreetree,建立一个新的树状数组tree1tree1tree1使得:tree1[i]=tree[i]∗(i−1)tree1[i]=tree[i]*(i-1)tree1[i]=tree[i]∗(i−1)
之后,x到y的区间和即为:
(y∗sum(tree,y)−(x−1)∗sum(tree,x−1))−(sum(tree1,y)−sum(tree1,x−1))(y*sum(tree,y)-(x-1)*sum(tree,x-1))-(sum(tree1,y)-sum(tree1,x-1))(y∗sum(tree,y)−(x−1)∗sum(tree,x−1))−(sum(tree1,y)−sum(tree1,x−1))
代码(树状数组)
#include <cstdio>
typedef long long ll;
ll n,a[100001],b[100001],m,o;
ll in(){
ll ans=0; char c=getchar(); int f=1;
while ((c<48||c>57)&&c!='-') c=getchar();
if (c=='-') f=-f,c=getchar();
while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
return ans*f;
}
void add(ll x,ll k){while (x<=n) a[x]+=k,x+=-x&x;}//插入
void add1(ll x,ll k){while (x<=n) b[x]+=k,x+=-x&x;}//插入
ll sum(ll x){ll ans=0; while (x) ans+=a[x],x-=-x&x; return ans;}//求和
ll sum1(ll x){ll ans=0; while (x) ans+=b[x],x-=-x&x; return ans;}//求和
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
n=in(); m=in(); ll x;
for (register int i=1;i<=n;i++)
x=in(),add(i,x-o),add1(i,(i-1)*(x-o)),o=x;//初值
while (m--){
ll l,r; char c=getchar();
while (c<65||c>90) c=getchar();
if (c=='C'){
l=in(); r=in(); x=in();
add(l,x); add(r+1,-x); add1(l,x*(l-1)); add1(r+1,-x*r);//差分思想
}
else {
l=in(); r=in(); ll ans=r*sum(r)-sum1(r)-(l-1)*sum(l-1)+sum1(l-1);//前缀和
if (ans<0) putchar('-'),ans=-ans;
if (ans) print(ans); else putchar('0'); putchar('\n');
}
}
return 0;
}
分析(线段树)
当然这道题可以用线段树去做,不过要满足区间修改和区间查询需要用懒标记(延迟标记)……
代码(线段树)
#include <cstdio>
typedef long long ll;
ll w[400001],lazy[400001],a[100001],n,m;
ll in(){
ll ans=0; char c=getchar(); int f=1;
while ((c<48||c>57)&&c!='-') c=getchar();
if (c=='-') f=-f,c=getchar();
while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
return ans*f;
}
void build(int k,int l,int r){//建线段树
if (l==r) {w[k]=a[l]; return;}
int mid=(l+r)>>1;
build(k<<1,l,mid); build(k<<1|1,mid+1,r);
w[k]=w[k<<1]+w[k<<1|1];
}
void ask(int k,int l,int r,int x,int y,ll &ans){//询问
if (l==x&&r==y) {ans+=w[k]; return;}
int mid=(l+r)>>1;
if (lazy[k]){//懒标记
w[k<<1]+=lazy[k]*(mid-l+1);
w[k<<1|1]+=lazy[k]*(r-mid);
lazy[k<<1]+=lazy[k];
lazy[k<<1|1]+=lazy[k];
lazy[k]=0;
}
if (y<=mid) ask(k<<1,l,mid,x,y,ans);
else if (x>mid) ask(k<<1|1,mid+1,r,x,y,ans);
else ask(k<<1,l,mid,x,mid,ans),ask(k<<1|1,mid+1,r,mid+1,y,ans);
}
void update(int k,int l,int r,int x,int y,int t){//更新
if (l==x&&r==y) {w[k]+=t*(r-l+1); lazy[k]+=t; return;}
int mid=(l+r)>>1;
if (lazy[k]){
w[k<<1]+=lazy[k]*(mid-l+1);
w[k<<1|1]+=lazy[k]*(r-mid);
lazy[k<<1]+=lazy[k];
lazy[k<<1|1]+=lazy[k];
lazy[k]=0;
}
if (y<=mid) update(k<<1,l,mid,x,y,t);
else if (x>mid) update(k<<1|1,mid+1,r,x,y,t);
else update(k<<1,l,mid,x,mid,t),update(k<<1|1,mid+1,r,mid+1,y,t);
w[k]=w[k<<1]+w[k<<1|1];
}
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
n=in(); m=in();
for (int i=1;i<=n;i++) a[i]=in();
build(1,1,n);
while (m--){
char c=getchar(); while (c<65||c>90) c=getchar();
int l=in(); int r=in();
if (c=='C') update(1,1,n,l,r,in());
else {
ll ans=0; ask(1,1,n,l,r,ans);
if (ans<0) putchar('-'),ans=-ans;
if (ans) print(ans); else putchar('0'); putchar('\n');
}
}
return 0;
}
分析(分块)
然而可以用一种时间较长但是码长短又直观的方法解决这个问题,那就是分块,采取大段维护,小段朴素的方法,时间复杂度O((n+q)logn)O((n+q)logn)O((n+q)logn)(因为超直观,就不解释了)
代码(分块)
#include <cstdio>
#include <cmath>
typedef long long ll;
ll sum[100001],add[100001],a[100001];
int l[321],r[321],n,m,t,pos[100001];
ll in(){
ll ans=0; char c=getchar(); int f=1;
while ((c<48||c>57)&&c!='-') c=getchar();
if (c=='-') f=-f,c=getchar();
while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
return ans*f;
}
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
void update(int x,int y,int d){
int p=pos[x],q=pos[y];
if (p==q){
for (register int i=x;i<=y;i++) a[i]+=d;
sum[p]+=d*(y-x+1);
}
else{
for (register int i=p+1;i<=q-1;i++) add[i]+=d;
for (register int i=x;i<=r[p];i++) a[i]+=d;
sum[p]+=d*(r[p]-x+1);
for (register int i=l[q];i<=y;i++) a[i]+=d;
sum[q]+=d*(y-l[q]+1);
}
}
ll ask(int x,int y){
int p=pos[x],q=pos[y];
ll ans=0;
if (p==q){
for (register int i=x;i<=y;i++) ans+=a[i];
ans+=add[p]*(y-x+1);
}
else {
for (register int i=p+1;i<=q-1;i++) ans+=sum[i]+add[i]*(r[i]-l[i]+1);
for (register int i=x;i<=r[p];i++) ans+=a[i];
ans+=add[p]*(r[p]-x+1);
for (register int i=l[q];i<=y;i++) ans+=a[i];
ans+=add[q]*(y-l[q]+1);
}
return ans;
}
int main(){
n=in(); m=in(); int t=sqrt(n);
for (register int i=1;i<=n;i++) a[i]=in();
for (register int i=1;i<=t;i++) l[i]=(i-1)*t+1,r[i]=i*t;
if (r[t]<n) t++,l[t]=r[t-1]+1,r[t]=n;
for (register int i=1;i<=t;i++)
for (register int j=l[i];j<=r[i];j++) pos[j]=i,sum[i]+=a[j];
while (m--){
char c=getchar(); while (c<65||c>90) c=getchar();
int x=in(); int y=in();
if (c=='C') update(x,y,in());
else {
ll ans=ask(x,y);
if (ans<0) putchar('-'),ans=-ans;
if (ans) print(ans); else putchar('0'); putchar('\n');
}
}
return 0;
}