前缀和***差分法******

*********前缀和*********

1、什么是前缀和?

陶陶学了数组以后,他对数组之间的累加和特别感兴趣,于是他就提出了要求 数组 元素之间的累加和的问题。

例如:现在有长度为 10 的数组: 3 2 5 1 4 5 3 7 8 2

从第2个元素到 第 5个元素的和就是 2+5+1+4=12

从第4个元素到第9个元素的和就是 1+4+5+3+7+8=28

输入格式

第一行 n k 两个正整数,分别表示 数组有n个元素,k次求和查询 (0<=n,k<=100000)

第二行为n个整数 数组的n个元素

以下有k行,每行两个元素,为求和的起点和终点

输出格式

k行,每行为起点到终点的和.

input

10 2
3 2 5 1 4 5 3 7 8 2
2 5
4 9

output

12
28

 题解:

对于给出的一段n个整数数列,给出k次查询,每次查询给出区间【L,R】,让求区间内的整数之和对于这道题,我首先想到的是最简单的,题目怎么说,我就怎么做,先整一个for循环读入n个整数,然后在每次查询所给出的L,R之间写一个for循环进行区间值的加和。但我忽略了时间复杂度,这个方法对于小数据的加和可行,但是对于大数据就会出现超时问题,该时间复杂度为O(n*n),那么就需要降低时间复杂度,我们可以把计算区间和的那一层for循环给省掉,我们可以在读入n个整数时进行加和处理,把前i个数的和相加后赋给数组第i个元素。这样,我们在每次查询求区间和时只需计算a[R]-a[L-1]即可。前缀和顾名思义就是前面i个数的总和。

以下是代码对比:

#include <iostream>
using namespace std;
int array[100005];
int main() {
    int n, k, sum;
    cin >> n >> k;
    int i, start, end, j;
    for (i = 1; i <= n; i++) {
        cin >> array[i];
    }
    for (i = 1; i <= k; i++) {
        cin >> start >> end;
        sum = 0;
        for (j = start; j <= end; j++) {
            sum = sum + array[j];
        }
        cout << sum << endl;
    }

    return 0;
}
#include<iostream>
int nums[100002],sum;
using namespace std;
int main(){
	int n,k;
	cin>>n>>k;
	int i,start,end;
	for(i=1;i<=n;i++){
		int t;
		cin>>t;
		sum=sum+t;
		nums[i]=sum;
	}
	for(i=1;i<=k;i++){
		cin>>start>>end;
		cout<<nums[end]-nums[start-1]<<endl;
	}
	return 0;
}

给你一串长度为n的数列a1,a2,a3......an,要求对a[L]~a[R]进行m次操作:

操作一:将a[L]~a[R]内的元素都加上P

操作二:将a[L]~a[R]内的元素都减去P

最后再给出一个询问求a[L]-a[R]内的元素之和?

你会怎么做呢?你可能会想,我对于m次操作每次都遍历一遍a[L]~a[R],给区间里的数都加上P或减去P,最后再求一次前缀和就行了。没错,这样子确实也能得出正确答案,但时间复杂度却高达O(M*n+q),对于1<=n,m<=1e5这个数据范围来说直接就tle了,所以说这个方法不可行。既然这样不行的话,那我们要怎么做才能快速的得到正确答案呢?是的,这个时候我们的差分就该派上用场了,我们新开一个数组b,储存每一次的修改操作,最后求前缀和的时候统计一下就能快速的得到正确答案了,详细请看下面代码。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+9;
int a[maxn],b[maxn];
int main(){
    int i,j,k,n,m,p;
    cin>>n>>m;
    for(i=1;i<=n;i++){
        cin>>a[i];
    }
    for(i=1;i<=m;i++){
        int L,R,t;
        cin>>t>>L>>R>>p;
        if(t==1){
            b[L]+=p;b[R+1]-=p; //仔细想想为什么b[R+1]要减去p
        }
        else{
            b[L]-=p;b[R+1]+=p;
        }
    }
    int add=0;
    for(i=1;i<=n;i++){
        add+=b[i];
        a[i]+=a[i-1]+add;
    }
    int x,y;
    cin>>x>>y;
    cout<<a[y]-a[x-1]<<endl;
}

相信看到这里,大家已经仔细思考过代码了,为什么操作一时b[R+1]要减去p,很简单,因为操作一我只需对[L,R]区间里的数加p,[R+1,n]这个区间里的数没必要加p,所以需要减掉p。

定义:

对于已知有n个元素的离散数列d,我们可以建立一个记录它的每项与前一项差值的差分数组f;可见,f[1]=d[1]-0;对于2<=i<=n;f[i]=d[i]-d[i-1];将原本对d数列的操作转移到对f数列的操作,最终通过合并f数列来求得对应加和后的的数列。d[i]=f[i]+f[i-1]+......+f[1];这样叫做差分法。

例子:

(1)现有一个序列:3 4 1 5 6 2 7 9

(2)第一步根据公式f[i]=d[i]-d[i-1]求f序列数组,初始化d[0]=0;所以差分数列f:3 1 -3 4 1 -4 5 2

(3)第二步需要对区间[1,6]的所有元素加上1(另d[1]=d[i]+1;d[7]=d[7]-1;),得f数列的值f:4 1 -3 4 1 -4 4 2

(4)根据公式d[i]=f[i]+f[i-1]+f[i-2]+....f[1];得f:4 5 2 6 7 3 7 9

题解:

我们只操作了两步,另d[L]=d[L]+p(想要加的值);d[R+1]=d[R+1]-p;只需要关注左右边界即可

至于为什么这么做呢?

原理:

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值