(模板)差分&&前缀和

差分数组在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\n",query(1,1,n,1,p));
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/FrankChen831X/p/12427963.html

时间: 2024-07-31 15:29:45

(模板)差分&&前缀和的相关文章

HDU 5452——Minimum Cut——————【树链剖分+差分前缀和】

Minimum Cut Time Limit: 3000/2000 MS (Java/Others)    Memory Limit: 65535/102400 K (Java/Others)Total Submission(s): 895    Accepted Submission(s): 387 Problem Description Given a simple unweighted graph G (an undirected graph containing no loops nor

HDU 5419——Victor and Toys——————【线段树|差分前缀和】

Victor and Toys Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/131072 K (Java/Others)Total Submission(s): 654    Accepted Submission(s): 219 Problem Description Victor has n toys, numbered from 1 to n. The beauty of the i-th toy is wi

POJ - 3263 差分+前缀和

只需不断维护相对值的前缀就能得到解 这种思想第一次是在树状数组区间更新那里看到的,由于题目要求是1~n所以直接可以用前缀和维护 注意不能直接-1 +1 还有POJ的数据..要不是书里有提谁知道会这么毒瘤 /*H E A D*/ int delta[maxn]; map<P,int> vis; int main(){ int n,I,h,r,a,b; while(~iin(n)){ I=read();h=read();r=read(); memset(delta,0,sizeof delta);

二维差分前缀和——cf1202D(好题)

直接枚举每个点作为左上角是可以做的,但是写起来较麻烦 有一种较为简单的做法是对一列或一行统计贡献 比如某一行的B存在的区间是L,R那么就有三种情况 1.没有这样的区间,即一行都是W,此时这行对答案的贡献一直是1 2.R-L+1<=k,那么这一段必须要找一个点代表的矩形来覆盖,可以求出这样的点的存在区间是一个矩形,当且仅当点在这个矩形范围内时,这一行会有1的贡献. 3.R-L+1>k,永远不会有贡献 对于情况2,我们用二维的差分来统计一下,最后枚举每个点,看我们选择这个点代表的矩形时,贡献是否达

【差分+前缀和】BZOJ1637: [Usaco2007 Mar]Balanced Lineup

Description Farmer John 决定给他的奶牛们照一张合影,他让 N (1 ≤ N ≤ 50,000) 头奶牛站成一条直线,每头牛都有它的坐标(范围: 0..1,000,000,000)和种族(0或1). 一直以来 Farmer John 总是喜欢做一些非凡的事,当然这次照相也不例外.他只给一部分牛照相,并且这一组牛的阵容必须是“平衡的”.平衡的阵容,指的是在一组牛中,种族0和种族1的牛的数量相等. 请算出最广阔的区间,使这个区间内的牛阵容平衡.区间的大小为区间内最右边的牛的坐标

模板:前缀和

http://lfyzit.com/problem/10 1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int a[90005], num, l, r, m, n; 5 int kd(){ 6 int r=0, f=1; 7 char c=getchar(); 8 while(c<'0'||c>'9'){ 9 if(c=='-') f=-1; 10 c=getchar(); 11 } 1

前缀和&amp;差分

一:差分数组概念 一.差分数组的定义及用途 1.定义:对于已知有n个元素的数列d,建立记录它每项与前一项差值的差分数组f:显然,f[1]=d[1]-0=d[1];对于整数i∈[2,n],我们让f[i]=d[i]-d[i-1].//f[i]数组为差分数组,d[i]数组为原数组 2.简单性质:(1)计算数列各项的值:观察d[2]=f[1]+f[2]=d[1]+d[2]-d[1]=d[2]可知,d[i]=f[i]的前缀和.(2)计算数列每一项的前缀和:第i项的前缀和即为数列前i项的和,那么推导可知 /

NOIP2012借教室[线段树|离线 差分 二分答案]

题目描述 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要 向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借教室的信息,我们自然希望编程解决这个问题. 我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借.共有m份 订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租 借教室(包括第sj天和第tj天),每天需要租借dj个教室. 我们假定,租借者对教室的大小.地点没

差分简单题集

[一维差分]Codeforces 1000C Covered Points Count 题目大意: 给定$n$个线段,给定这些线段所在的区间($l,r\leq10^{18}$),这些线段能够覆盖它们所包含的点,问你被包含$[1,n]$次的点分别有多少个. 解题分析:用差分来高效的统计一下指定区间内所被覆盖的线段个数,同时,因为$l,r$的大小非常大,所以我们需要对所有的线段进行离散化. #include <bits/stdc++.h> using namespace std; template