前缀和&&差分
这算两个小技巧吧,下面就给大家介绍一下他们的用处。
前缀和
一维前缀和
题意:
一个长度为 n 的数组a,有 q 次询问, 每次询问有两个数 l r,问数组中区间 [l,r]之间的数之和。
示例:
输入:
第一行两个数,分别是 n 和 q,
第二行是 n 个数, 代表数组 a,
接下来是 q 行, 每行两个数 l r,
5 3
1 2 3 4 5
1 2
2 2
1 5
输出:
3
2
15
定义一个数组 sum[i] , 代表前 i 个数之和,如果想求区间 [l, r] 的数之和,直接可以由 sum[r] - sum[l-1]
得到。即前 r
个数之和减去前 l - 1
个数之和,就是区间的数之和。
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n,q,l,r;
n = cin.nextInt(); // 读入
q = cin.nextInt(); // 读入
int[] a = new int[n+1];
int[] sum = new int[n+1];
for (int i = 1; i <= n; ++i)
a[i] = cin.nextInt(); // 读入数组
sum[0] = 0;
for (int i = 1; i <= n; ++i) // 计算前缀和
sum[i] = sum[i-1] + a[i];
for (int i = 1; i <= q; ++i){
l = cin.nextInt(); r = cin.nextInt();
System.out.println(sum[r] - sum[l-1]); // 输出答案
}
}
二维前缀和
题意:
有 n 行 m 列的二维数组 a,每次询问给定四个数字 x1 y1 x2 y2, 分别代表矩形的左上角和右下角,求矩形内的数字和。
示例:
输入:
第一行 n m q,
接下来 n 行, 每行 m 个数字,
接下来 q 行, 每行四个数字的询问。
4 4 2
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
1 1 2 2
输出:
1
4
定义一个数组 sum[i][j]
, 代表 0,0
为左上角 i,j
为右下角的矩形数字和,如果想求 x1 y1 x2 y2 矩形的数之和,直接可以由 sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]
得到。
由上图可知, 如果我们想得到红色的部分, 就要拿 天蓝色 - 黄色 - 粉红色 + 青色
具体我们看一下代码:
public class Solution {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n,m,q,x1,x2,y1,y2;
n = cin.nextInt(); // 读入
m = cin.nextInt();
q = cin.nextInt(); // 读入
int[][] a = new int[n+1][m+1];
int[][] sum = new int[n+1][m+1];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
a[i][j] = cin.nextInt();
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] + a[i][j]; // 这个可以自己画图理解一下。
for (int i = 1; i <= q; ++i){
x1 = cin.nextInt(); y1 = cin.nextInt();
x2 = cin.nextInt(); y2 = cin.nextInt();
System.out.println(sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]); // 输出答案
}
}
}
差分
题意:
有一个长度为 n 的数组 a, 一开始数组所有的值都是 0, 有 m 次操作,每次操作有三个数字 l r w, 即给区间[l,r] 内所有的值都加上 w, 最后输出 a 数组。
示例:
输入:
第一行 两个数字 n m
下面 m 行, 每行三个数字 l r w
5 3
1 1 1
1 3 4
3 5 2
输出:
5 4 6 2 2
很暴力的做法:
每次的操作, for 循环从 l 到 r, 对于其中的每个值直接加上 w 就可以了。
差分的做法:
每次的操作, a[l] += w, a[r+1] += -w, 最后来一遍 for 循环做个前缀和, 就是想要的答案了。
我们来想一下,差分的做法为什么对呢!!!
在 l 的位置上 +w, 在 r+1 的位置 + (-w), 这样做前缀和的时候,w + (-w) = 0, 这样 w 就只会影响区间 [l, r] 里面的位置。
举个例子:
举个例子
3 6 5 // [3 6] 加上5
1 2 3 4 5 6 7 8 // 下标
0 0 5 0 0 0 -5 0 // 对应位置加减 5
0 0 5 5 5 5 0 0 // 前缀和
我们可以看到对于区间 [3, 6] 加上 5 这个操作,差分 + 前缀和是正确的。
可以在深入的想一下,每个区间的加操作是独立的,最后只做一遍前缀和。那么最后一遍的前缀和就可以保证每次区间加操作的正确。
再看个例子:
3 6 5 //[3 6] 加上 3
5 9 2 //[5 9] 加上 2
图片第二行为前缀和
public class Solution {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n,m,l,r,w;
n = cin.nextInt(); // 读入
m = cin.nextInt();
int[] a = new int[n+10];
for (int i = 1; i <= m; ++i) {
l = cin.nextInt();
r = cin.nextInt();
w = cin.nextInt();
a[l] += w;
a[r+1] -= w;
}
for (int i = 1; i <= n; ++i)
a[i] = a[i-1] + a[i]; // 在同一个数组中计算前缀和
for (int i = 1; i <= n; ++i)
System.out.print(a[i] + " ");
System.out.println();
}
}
总结
前缀和 && 差分 都是解题的思想,大家了解一下就可以了。
有些地方用这些方法可以有意想不到的效果。