首先,在文章的开头,我们先思考一下,如果给你一个区间,每次都将这个区间中的两个端点的数据修改,那么,都有哪些方法呢?
1.暴力求解
如果采用暴力求解的话,编码简单粗暴,直接就在给定的两个端点之间修改,比如,给定修改L和R这两个端点的区间内都加上d,那么就之间for循环从L循环到R。但暴力求解的缺点很明显,也很致命,那就是时间复杂度大,一次修改的时间复杂度为O(n),但如果要修改t次的话,那么时间复杂度就增大到O(tn),那么基本上很多数据都过不了了。所以,在竞赛的时候,如果不是时间不够的话,不用轻易用暴力。
2.前缀和与差分
这就是本章的重中之重,用了前缀和与差分后那么一次修改的时间复杂度就是O(1),n次修改就是O(n)了。那么,这个神奇的前缀和与差分又是怎么做的可以将一次修改O(n)的时间复杂度变为O(1)的呐?
首先,我们先引入前缀和的概念,什么是前缀和呢?
前缀和和差分都是一种常用的算法,主要用于快速计算序列中的区间和或者序列元素的变化量。它们的具体定义和概念如下:
-
前缀和:对于一个序列a,它的前缀和序列s的第i个元素表示序列a中前i个元素的和,即s[i]=a+a+…+a[i]。通过预处理前缀和数组s,可以快速计算出任意区间[l,r]中的元素之和,即sum[l,r]=s[r]-s[l-1]。
-
差分:对于一个序列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。
打字不易,望家人们可以三连,谢谢。
(侵删)