(模板)差分&&前缀和

Posted frankchen831x

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(模板)差分&&前缀和相关的知识,希望对你有一定的参考价值。

差分数组在acm中有很广泛的应用。对于原数组a:1 2 3 5 5,其差分数组就是sub:1 1 1 2 0,即每一项与前一项的差。其性质有:

  • 差分数组求前缀和能得到原数组
  • 对区间[l,r]上都加上d在差分数组上表现为sub[l]+=d , sub[r+1]-=d
  • 进一步的,在[l,r]上加上首项为k,公差为d的等差数列,在差分数组的表现为sub[l]+=k , sub[i]+=d (l+1<=i<=r) , sub[r+1]-=k+(r-l)*d

对于上述第三条性质,如果是离线维护区间加等比数列的问题,即执行完所有的修改操作,然后求原数组或者数组中某一项。就可以使用二阶差分,每次操作给出l、r、a、k表示从l到r依次加上一个首项为a,公差为k的等差数列。维护二阶差分数组d2代码如下:

void add(int l,int r,int a,int k){
    d2[l]+=a;
    d2[l+1]+=k-a;
    d2[r+1]-=(r-l+1)*k+a;
    d2[r+2]-=(l-r)*k-a;
}

求前缀和的操作如下:

void pre_sum(){
    for(int i=1;i<=n;++i)
    {
        d2[i]+=d2[i-1];
    }
}

因此还原数组的操作:

pre_sum();
pre_sum();

离线操作的话复杂度即O(m)。(m为操作次数)

 


如果对于第三条性质,是在线操作,即需要修改的同时查询数组中某一项的值,可以直接使用第三条性质所述的维护一阶差分数组,查询即求前缀和。用线段树来维护。复杂度O(mlogn),m是操作次数,n为数组大小。比如luogu1438:

 

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=1e5+5;
int n,m,a[maxn],sub[maxn];
int tr[maxn<<2],lz[maxn<<2];

void pushup(int u){
    tr[u]=tr[u<<1]+tr[u<<1|1];
}

void pushdown(int u,int l,int r){
    int mid=(l+r)>>1;
    tr[u<<1]+=lz[u]*(mid-l+1);
    lz[u<<1]+=lz[u];
    tr[u<<1|1]+=lz[u]*(r-mid);
    lz[u<<1|1]+=lz[u];
    lz[u]=0;
}

void build(int u,int l,int r){
    if(l==r){
        tr[u]=sub[l];
        return;
    }
    int mid=(l+r)>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    pushup(u);
}

void update(int u,int l,int r,int L,int R,int v){
    if(l>=L&&r<=R){
        tr[u]+=v*(r-l+1);
        lz[u]+=v;
        return;
    }
    if(lz[u]) pushdown(u,l,r);
    int mid=(l+r)>>1;
    if(L<=mid) update(u<<1,l,mid,L,R,v);
    if(R>mid) update(u<<1|1,mid+1,r,L,R,v);
    pushup(u);
}

int query(int u,int l,int r,int L,int R){
    if(l>=L&&r<=R){
        return tr[u];
    }
    if(lz[u]) pushdown(u,l,r);
    int ret=0,mid=(l+r)>>1;
    if(L<=mid) ret+=query(u<<1,l,mid,L,R);
    if(R>mid) ret+=query(u<<1|1,mid+1,r,L,R);
    pushup(u);
    return ret;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)
        sub[i]=a[i]-a[i-1];
    build(1,1,n);
    while(m--){
        int op,l,r,k,d,p;
        scanf("%d",&op);
        if(op==1){
            scanf("%d%d%d%d",&l,&r,&k,&d);
            update(1,1,n,l,l,k);
            if(r>l) update(1,1,n,l+1,r,d);
            if(r!=n) update(1,1,n,r+1,r+1,-k-(r-l)*d);
        }
        else{
            scanf("%d",&p);
            printf("%d
",query(1,1,n,1,p));
        }
    }
    return 0;
}

 

以上是关于(模板)差分&&前缀和的主要内容,如果未能解决你的问题,请参考以下文章

差分[差分数组 & 树状差分]

前缀和序列 & 差分序列

Vijos P1782 借教室 ( 前缀和&&差分序列)

前缀和&差分

二维前缀和差分+离散化

前缀和与差分java模板代码