一、一维前缀和
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,合理的使用前缀和与差 分,可以将某些复杂的问题简单化。
前缀和可以简化代码,使时间复杂度降低。
比如:
输入一个长度为 n 的整数序列。接下来再输入 m 个询问,每个询问输入一对 l , r 。对于每个询问,输 出原序列中从第 l 个数到第 r 个数的和。
暴力做法:对于m次询问,从 l 到 r 累加求和。时间复杂度为O(m*n) 如果 n 和 m 的数据量稍微大一点就有可能超时,而我们如果使用前缀和的方法来做的话就能够将时 间复杂度降到 O(n + m) ,大大提高了运算效率。
前缀和做法:先预处理出前缀和数组,再进行m次查询,时间复杂度为O(m+n)
int a[N];
int sum[N]; // sum[i] 表示从数组a的前i项和,sum[i]=a[1]+a[2]+...+a[i];
sum[0]=a[0];
for (int i = 1; i <= N; i++)
{
sum[i] = sum[i - 1] + a[i];
}
while (m--)
{
int l, r;
cin >> l >> r;
cout << sum[r] - sum[l - 1] << "\n"; // 前r项减去前l-1项
}
所用到的公式就是:
sum[r] = a[1] + a[2] +...+ a[l - 1] + a[l] + a[l + 1] + ... + a[r];
sum[l - 1] = a[1] + a[2] + ... + a[l - 1];
所以[l,r]的和为:sum[r] - s[l - 1] = a[l] + a[l + 1] + ... + a[r];
二、二维前缀和
对于二维数组s,s[i][j]表示从左上角(1,1)到右下角(i,j)的整个的和
要求这样的一个和,我们可以用双重循环来实现暴力解决,也可以利用推导公式来解决;
下面推导一下:
原二维数组就看作是一个大的矩阵,我们要求的前缀和就相当于是一个小矩阵的面积,我们只需要用大矩阵的面积减去剩余部分的面积就可以得到答案;
图示:
由此我们可以得到:
蓝色矩形面积 s[i][j] = 绿色面积 s[i - 1][j] + 紫色面积 s[i][j - 1] - 重复加的红色的面积 s[i - 1][j - 1] + 小方块的面积 a[i][j] ;
所以我们得到前缀和公式:
s[i][j] = s[i - 1][j] + s[i][j - 1] + a[i] [j] - s[i - 1][j - 1]
继续推广,当我们要求以(x1,y1)(左上角)与(x2,y2)(右下角)为端点的矩阵和的时候,就有:
S=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]
三、一维差分
(I)、差分实际上就相当于是前缀和的逆运算
(II)、例如:
给定区间 [l, r ] ,让我们把 a 数组中的 [l, r] 区间中的每一个数都加上 c ,即 a[l] + c , a[l + 1] + c , a[l + 2] + c ...... a[r] + c ;
(1).暴力做法:
for 循环 l 到 r 区间,时间复杂度 O(n) . 如果我们需要对原数组执行 m 次这样的操作,时间复杂度就会变成 O(n * m)
(2).考虑高效的差分做法 :
sum数组是a数组的前缀和数组,对a数组的 a[i] 的修改,会影响到sum数组中从 sum[i] 及往后的 每一个数。
(III)、一维差分结论:
给sum数组中的 [ l, r] 区间中的每一个数都加上 c ,只需对差分数组 a 做
a[l] + = c; a[r+1] - = c; //时间复杂度为O(1)
这个操作可以和看成是先全部或局部做变化,然后再把不需要变的那一部分变回去,这里只是为了便于理解二维数组的差分,有时一维数组的差分可以直接对 l 到 r 的值进行改变。
四、二维差分
(I)二维差分其实可以理解为对二维数组局部做变化,对于一些简单的可以直接用双重循环改变,但是,有些时候这样会很费时。对于二维数组的差分我们通常用下面的方法:
d[x1][y1] += v;
d[x2 + 1][y1] -= v;
d[x1][y2 + 1] -= v;
d[x2 + 1][y2 + 1] += v;
(II)对二维数组差分的剖析:
五、下面找一个题来练一下
(I)、一维前缀和
(1).题目链接:4789. 前缀和序列 - AcWing题库
(做这类题时需要注意下标的对应关系)
(2).代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int x, y, z;
void PrintSum(int *a, int *sum, int n)
{
int i;
sum[1] = a[1];
for (i = 2; i <= n; i++)
sum[i] = sum[i - 1] + a[i];
cout << sum[z] - sum[y - 1] << endl;
}
signed main()
{
int n, i, j;
cin >> n;
int arr[n + 1], brr[n + 1];
for (i = 1; i <= n; i++)
{
cin >> arr[i];
brr[i] = arr[i];
}
arr[0] = 0;
brr[0] = 0;
sort(brr + 1, brr + n + 1);
int m;
cin >> m;
while (m--)
{
int sum[n];
sum[0] = 0;
cin >> x >> y >> z;
if (x == 1)
PrintSum(arr, sum, n);
else if (x == 2)
PrintSum(brr, sum, n);
}
return 0;
}
(II)、一维差分
(1).题目链接:736. 安迪种树 - AcWing题库
(2).代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
int n, q, i, j;
cin >> n >> q;
int arr[n + 1] = {0};
while (q--)
{
int l, r;
cin >> l >> r;
for (i = l; i <= r; i++)
arr[i] += 1;
}
for (i = 1; i <= n; i++)
cout << arr[i] << " ";
return 0;
}