总结 :前缀和多用于求区间和 原数组--->前缀和数组--->修改前缀和数组---->(修改过后)原数组
差分多用于区间修改和单点查询问题 原数组---->差分数组---->修改差分数组---->(修改过后)原数组
前提:数组均从下标1位置开始存数
a[1]~a[n] a[1][1]~a[n][m]
-------
有一个含有n个数据的数组a[1]~a[n]
给出m个区间[l,r] 对于每个区间,输出该区间中数据的和
eg. n=4, m=2 4 3 2 1
1,4--->10 2,3--->5
暴力破解
l[i] r[i] :储存第i个区间的范围 lrsum[i]:第i个区间的和
for(int i=1;i<=m;i++)
{
//for(int j=l[i] ; j<=r[i] ; j++) lrsum[i]+=j;
优化: lrsum[i]=sum[r[i]]-sum[l[i]-1] }
O(m*n) 可能超时
可以优化内层循环 优化方案:前缀和:

a数组的前缀和数组sum:
![]()
那么求出前缀和数组有什么用呢?
eg.求a[3]~a[9]的和: sum[3]=a1+a2+a3 sum[9]=a1+a2+…+a9 sum[2]=a1+a2
所以 a[3]~a[9]的和为 sum[9]-sum[2] //因为a[3]~a[9]是闭区间,a[3]是要包含进去的,所以只能减到a[2]
那么求a[l]~a[r]的和: sum[r]-sum[l-1]
总结:前缀和一般是用来进行数组的预处理,提高后续的操作效率,多用于求区间和
而且前缀和也只是一种对数组进行预处理的方式
二维数组的前缀和
eg.求a[1][1]~a[i][j]的前缀和:不是把每一行累加,而是算这两个点构成的矩形之和

这里引入一个概念
基于这种思想,我们想到a[i][j]之前已经计算过的 sum[i-1][j],sum[i][j-1]
把它们两个加起来发现有重叠部分(红色) 那就要减去它(sum[i-1][j-1]) 最后再加上自己

得公式 sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]
这样就规避了四层循环(i,j递增两次,1到i和1到j两次) 只有两层循环
要点1:公式不用记,记住推理过程
2.sum[][]必须全部初始化为0(全局变量自动赋值为0) 并且下标也必须从1开始,从0开始会越界
求a[x][y]~a[i][j]区间的和 同理:减去黄色部分就行了


ans=sum[i][j]-sum[i][y-1]-sum[x-1][j]+sum[x-1][y-1]


//这道题还有一个很坑人的就是它是用点表示边长,比如a[1][1]~a[1][3],边长为3: 3-1+1 从案例中得到
#include<iostream>
using namespace std;
int arr[105][105];
int sum[105][105];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> arr[i][j];
}
}
//求每个点的前缀和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + arr[i][j];
}
}
int l; //记录正方形当前边长,每次循环找到一个正方形就行了
for (l = 1; l <= min(m, n); l++)
{
int flag = 0;
for (int i = 1; i <= n - l+1; i++) {
for (int j = 1; j <= m - l+1; j++) { //(i,j)表示左上顶点
//右下顶点
int rx = i + l-1, ry = j + l-1;
//找只包含 0 和 1 的矩阵里找出一个不包含 0 的正方形,结合所学知识,
//是不是只要前缀和为l*l就行了
if (sum[rx][ry] - sum[rx][j - 1] - sum[i - 1][ry] + sum[i - 1][j - 1] == l*l) {
flag = 1;
break;
}
}
if (flag) break;
}
if (flag == 0) {
cout << l - 1;
return 0;
}
}
}
-------
差分: 应用:对数组进行区间修改时避免用循环
有一个含有n个数据的数组a[1]~a[n]
给出m个区间[l,r] 对于每个区间,对于每个区间中的所有数据都+1
输出最终的a数组
n=6,m=3
1 3 2 7 6 8
[3,5]--->1 3 3 8 7 8 [1,4]--->2 4 4 9 7 8
l[i] r[i] :储存第i个区间的范围 数组a
for(int i=1;i<=m;i++)
{
//for(int j=l[i] ; j<=r[i] ; j++) a[j]++; }
O(n*m)
优化方法: 差分数组
差分:前缀和的逆运算 已知sum[n],求原数组(也叫做差分数组)
a[1]=sum[1] a[2]=sum[2]-sum[1] a[i]=sum[i]-sum[i-1]
这跟题目有什么关系呢? 举个例子
a[1] a[2] a[3] a[4] a[5] a[6] -----差分数组(相对于前缀和数组来说,前缀数组就是差分数组求前缀和求出来的)
sum[1]~~sum[6]
a[1] a[2] a[3]+1 a[4] a[5] a[6]
sum[1]=a[1] sum[2]=a[1]+a[2]
sum[3]=sum[2]+a3+1 sum[4]=a[1]+ a[2] + a[3]+1 +a[4]
sum[5]=a[1] +a[2]+ a[3] + 1+a[4] + a[5]
sum[6]= a[1]+ a[2] + a[3]+1 + a[4] + a[5] + a[6]
我只是在a[3]一个数字上+1,导致sum[3]后面的数字都加了1
并且不会影响递推性,a[3]=sum[3]-sum[2] a[4]=sum[4]-sum[3] //1给约掉了
结论:我在差分数组进行单点修改,其前缀和数组会进行相应的区间修改
a[j]+k sum[j]+x~~sum[n]+x
若要求:sum[l]+x~~sum[r]+x----->a[l]+x &&a[r+1]-x //sum[r]上也加了x,这是闭区间,所以往后挪
回到最初的问题
1 3 2 7 6 8
[3,5]--->1 3 3 8 7 8
[1,4]--->2 4 4 9 7 8 怎么优化?
把1 3 2 7 6 8当作前缀和数组,我去求它的差分数组b[],然后再差分数组上进行操作
最后转化为前缀和数组,就是修改后的原数组
[3,5]--->1 3 3 8 7 8 ---->b[3]+1 b[6]-1
[1,4]--->2 4 4 9 7 8----> b[1]+1 b[5]-1
这样等价后我相当于只进行了两个单点修改,就不需要遍历来修改原数组
所以我感觉核心就是对数组的某个区间内进行加减某个数,可以转化为对差分数组的两个单点加减
二维差分数组
二维数组的某个面进行加减某个数(把它当成前缀和数组),可以转化为对差分数组的四个单点加减
已知sum[][] 求a[][]

a[i][j]=sum[i][j]-sum[i][j-1]-sum[i-1][j]+sum[i-1][j-1] sum<--a<--b
那么我差分数组已知,我想让红色部分全部+1,怎么做?

如果差分数组a[x][y]++,那么受到影响的前缀和有哪些呢(前缀和包含了a[x][y]的
即从a[x][y]到a[m][n]差分点的前缀和)

那么我只想要红色矩形区域的点的前缀和受影响, b[x][y]++,b[x+1][y]-- ,b[x][y+1]--,b[x+1][y+1]++
例题:

//在某个范围内对数据进行相加减,可以转为对它的差分数组的几个点进行相加减
#include<iostream>
using namespace std;
int arr[1005][1005];
int a[1005][1005]; //arr的差分数组,都是0
int main()
{
int n, m;
cin >> n >> m;
int x1, y1, x2, y2;
for (int i = 1; i <= m; i++) {
cin >> x1 >> y1 >> x2 >> y2;
//一般思路是两层循环遍历地毯加上去,但是这样会超出时间
a[x1][y1]++;
a[x2 + 1][y1]--;
a[x1][y2 + 1]--;
a[x2 + 1][y2 + 1]++; //n=1000也没关系
}
//差分数组求前缀和得到原数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
arr[i][j] = arr[i - 1][j] + arr[i][j - 1] - arr[i - 1][j - 1] + a[i][j];
cout << arr[i][j] << " ";
}
cout << "\n";
}
}

170

被折叠的 条评论
为什么被折叠?



