c++/c语言之前缀和与差分(区间修改和询问)

        首先,在文章的开头,我们先思考一下,如果给你一个区间,每次都将这个区间中的两个端点的数据修改,那么,都有哪些方法呢?

        1.暴力求解

        如果采用暴力求解的话,编码简单粗暴,直接就在给定的两个端点之间修改,比如,给定修改L和R这两个端点的区间内都加上d,那么就之间for循环从L循环到R。但暴力求解的缺点很明显,也很致命,那就是时间复杂度大,一次修改的时间复杂度为O(n),但如果要修改t次的话,那么时间复杂度就增大到O(tn),那么基本上很多数据都过不了了。所以,在竞赛的时候,如果不是时间不够的话,不用轻易用暴力。

        2.前缀和与差分

        这就是本章的重中之重,用了前缀和与差分后那么一次修改的时间复杂度就是O(1),n次修改就是O(n)了。那么,这个神奇的前缀和与差分又是怎么做的可以将一次修改O(n)的时间复杂度变为O(1)的呐?

        首先,我们先引入前缀和的概念,什么是前缀和呢?

        前缀和和差分都是一种常用的算法,主要用于快速计算序列中的区间和或者序列元素的变化量。它们的具体定义和概念如下:

  1. 前缀和:对于一个序列a,它的前缀和序列s的第i个元素表示序列a中前i个元素的和,即s[i]=a+a+…+a[i]。通过预处理前缀和数组s,可以快速计算出任意区间[l,r]中的元素之和,即sum[l,r]=s[r]-s[l-1]。

  2. 差分:对于一个序列a,它的差分序列d的第i个元素表示序列a中相邻两个元素之差,即d[i]=a[i+1]-a[i]。通过对差分数组d进行前缀和运算,可以还原出原始序列a,即a[i]=d+d+…+d[i]。

        前缀和和差分算法在数据结构、数学等领域都有广泛的应用,如求解区间最大值、最小值、平均值、方差等问题,以及处理离散化、差分约束系统等问题。

        总的来说,前缀和与差分是一对逆运算。

        但需要注意的是,两者的区别主要在于作用不同,前缀和适用于求区间和问题,而差分适用于区间修改问题。

        3.树状数组

        树状数组也是不错的选择,总的时间复杂度为O(nlogn),树状数组在后面会讲,在这就不过多介绍了。

        4.前缀和与差分的操作

        那么接下来差分数组又该怎么操作呢?

        如何在a数组(记录差分数组D的前缀和数组)[L,R]内修改呐?

        很简单,执行D[L]+=d;D[R+1]-=d;即可

        我们来分析一下,对于任意a[x]=d[1]+d[2]+d[3]+···+d[x]有:

        (1)1<=x<L,前缀和a[x]不变;

        (2)L<=x<=R,前缀和a[x]比原本增加了d;

        (3)R<x<=N,前缀和a[x]不变,因为被D[R+1]-=d;给抵消掉了。

        下面我们以洛谷的一道例题(P2367 语文成绩)为例:

题目背景

语文考试结束了,成绩还是一如既往地有问题。

题目描述

语文老师总是写错成绩,所以当她修改成绩的时候,总是累得不行。她总是要一遍遍地给某些同学增加分数,又要注意最低分是多少。你能帮帮她吗?

输入格式

第一行有两个整数 n,p,代表学生数与增加分数的次数。

第二行有 n 个数,a1∼an​,代表各个学生的初始成绩。

接下来 p 行,每行有三个数,x,y,z,代表给第 x 个到第 y 个学生每人增加 z 分。

输出格式

输出仅一行,代表更改分数后,全班的最低分。

输入输出样例

输入 

3 2
1 1 1
1 2 1
2 3 1

输出 

2

说明/提示

对于 40% 的数据,有 𝑛≤10^3。

对于 60%的数据,有 𝑛≤10^4。

对于 80% 的数据,有 𝑛≤10^5。

对于 100%的数据,有 𝑛≤5×10^6,p≤n,学生初始成绩 ≤100,𝑧≤100。

本题是前缀和与差分的一道经典例题,可以之间套用模板:

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int n,p;
	cin>>n>>p;
	int *a=new int[n+1];
	int *D=new int[n+1];
	for(int i=0;i<=n;i++)
		D[i]=a[i]=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
		D[i]=a[i]-a[i-1];
	for(int i=1;i<=p;i++){
		int x,y,z;
		cin>>x>>y>>z;
		D[x]+=z;
		D[y+1]-=z;
	}
	int Min=0x3f3f3f3f3f3f;
	for(int i=1;i<=n;i++){
		a[i]=a[i-1]+D[i];
		Min=min(a[i],Min);
	}
	cout<<Min;
	return 0;
}

        代码如上,a,D数组分别是前缀和,差分数组。

        先将原本的分数用数组a储存,再分别计算差分数组D的值,最后p次,每次都修改[x,y]区间的分数,最后再查找数组a中分数最低的同学,就是答案了。

        差分也分很多种,有一维差分,二维差分,三维差分。接下来,我们讲一下二维差分。

        二维差分的定义以及概念

        

        二维差分是指对于一个二维矩阵,通过差分的方法,得到一个新的二维矩阵。差分就是将相邻元素的差值保存下来,以此构建一个新的数组或矩阵。二维差分通常用于解决二维区间求和、修改等问题。

        具体来说,对于一个二维矩阵A,设B为其对应的差分矩阵,则B[i][j] = A[i][j] - A[i-1][j] - A[i][j-1] + A[i-1][j-1]。通过这个公式,我们可以快速计算出原矩阵中某个区间的和,也可以快速进行区间修改。

        举个例子,假设我们有一个二维矩阵A,现在需要对其某个区间内的所有元素加上一个常数c,那么我们只需要对差分矩阵B中对应区间的四个角进行修改即可。具体来说,设要修改的区间为[x1,y1]到[x2,y2],则有B[x1][y1] += c, B[x1][y2+1] -= c, B[x2+1][y1] -= c, B[x2+1][y2+1] += c。最后根据差分矩阵B计算出修改后的原矩阵即可。

        修改二维差分的方法总结如下:

D[x][y]    +=d;
D[x][y+1]  -=d;
D[x+1][y]  -=d;
D[x+1][y+1]+=d;

        需要注意的是,二维差分的递推公式为:a[i][j]=D[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1];

        以下图为例:

        

        (图片来源:http://www.it610.com/

        我们讲这张图的四小块分别用S1,S2,S3,S4(左上,右上,左下,右下)来表示。

        那么a[1][1]=S1;a[2][1]=S1+S2;a[1][2]=S1+S3;a[2][2]=S1+S2+S3+S4;

        那么易得:a[2][2]=S2+a[1][2]+a[2][1]-a[1][1];

        其中a[1][1]就是a[1][2]+a[2][1]中多出的公共的那一块。

        下面给出一道例题,读者请自行完成。

        洛谷P3397。

        

        打字不易,望家人们可以三连,谢谢。

(侵删)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值