前缀和
什么是前缀和?
给定一个元素个数为n
的数组A
,数组元素为a1``, a2``, ... , an
。
前缀和是这样一个数组,其元素满足s1 = a1
s2 = a1 + a2
s3 = a1 + a2 + a3
...
sn = a1 + a2 + ... + an
也就是说s1 = s0+a1
s2 = s1 + a2
s3 = s2 + a3
...
sn = s(n-1) + an
注意:为了避免下标的转换,使原数组A下标从1开始计数。
前缀和的用处
给定原数组A,求A的某一段序列的和。
注意:两种给定序列区间的说法:
- 起始下标
i
和结束下标j
(都是闭区间) - 求数组A中第
l
个元素至第r
个元素的和(闭区间)
我们的前缀和数组直接对应第二种说法:result = s[r] - s[l-1]
如果是第一种说法,需要转换成第二种,方便理解。即l = i + 1, r = j + 1; result = s[r] - s[l-1]
一维数组前缀和
例题:
Acwing 795. 前缀和 | 数组 | 前缀和 |
---|---|---|
输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m行,每行包含两个整数 l 和 r,表示一个询问的区间范围。
输出格式
共 m 行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
import java.util.*;
public class Main {
static final int N = 100010;
static int[] pre = new int[N];
static int n, m;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 1; i <= n; i++) {
pre[i] = sc.nextInt();
pre[i] += pre[i - 1];
}
while (m-- > 0) {
int l = sc.nextInt(), r = sc.nextInt();
int res = query(l, r);
System.out.println(res);
}
}
static int query(int l, int r) {
return pre[r] - pre[l - 1];
}
}
二维数组前缀和
例题:
AcWing 796. 子矩阵的和 | 数组 | 前缀和 |
---|---|---|
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式
共 q 行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
import java.util.*;
public class Main {
static final int N = 1010;
static int[][] pre = new int[N][N];
static int n, m, q;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
q = sc.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int x = sc.nextInt();
pre[i][j] = x + pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1];
}
}
while (q-- > 0) {
int x1 = sc.nextInt(), y1 = sc.nextInt();
int x2 = sc.nextInt(), y2 = sc.nextInt();
int res = query(x1, y1, x2, y2);
System.out.println(res);
}
}
static int query(int x1, int y1, int x2, int y2) {
return pre[x2][y2] - pre[x2][y1 - 1] - pre[x1 - 1][y2] + pre[x1 - 1][y1 - 1];
}
}
差分
什么是差分?
差分是前缀和的逆运算,对于数组a和b来说,假设a是原数组,b是a数组的差分数组,那么对于b来说,a数组就是b数组的前缀和数组。
如果定义前缀和为函数f,有a = f(b), b = f``-1``(a)
差分的用处
如果想对数组a的一个子序列进行整体操作,使得该子序列的每个元素都+c
,如果不用差分来做,每次操作时间复杂度都为O(n),而使用差分数组b来做,只需要一次初始化O(n)操作,其它对子序列的整体操作时间复杂度都为O(1)
拥有数组b[n]
后,想要对a
数组中所有的数据加上c
,只需要将b[1]+c
即可,因为a[i]
是b[i]
的前缀和,a[i]=b[1]+b[1]+b[3]+……+b[n]
。
如果想将a
数组中[l,r]
部分的数据全部加上c
,只需要将b[l]+c,
然后b[r+1]-c
即可。
差分数组的构造也可以使用上述思想,即[l, r]
变成[i,i]
即可。
注意:同前缀和一样,数组都是从1开始的,如果原数组的个数为n
,那么差分数组的长度为n+2
,这样做的好处是方便边界处理(前后各一个)。
一维差分
Acwing 797. 差分 | 数组 | 差分 |
---|---|---|
输入一个长度为n
的整数序列。
接下来输入m
个操作,每个操作包含三个整数l, r, c
,表示将序列中[l, r]
之间的每个数加上c
。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
import java.util.*;
public class Main {
static final int N = 100010;
static int[] diff = new int[N];
static int n, m;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i = 1; i <= n; i++) {
int x = sc.nextInt();
update(i, i, x);
}
for (int i = 0; i < m; i++) {
int l = sc.nextInt(), r = sc.nextInt(), x = sc.nextInt();
update(l, r, x);
}
for (int i = 1; i <= n; i++)
diff[i] += diff[i - 1];
for (int i = 1; i <= n; i++)
System.out.print(diff[i] + " ");
}
static void update(int l, int r, int x) {
diff[l] += x;
diff[r + 1] -= x;
}
}
二维差分
例题:
Acwing 798. 差分矩阵 | 数组 | 差分 |
---|---|---|
输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数n,m,q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
import java.util.*;
import java.io.*;
public class Main {
static final int N = 1010;
static int[][] a = new int[N][N];
static int n, m, q;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String[] ss = br.readLine().split(" ");
n = Integer.parseInt(ss[0]);
m = Integer.parseInt(ss[1]);
q = Integer.parseInt(ss[2]);
for (int i = 1; i <= n; i++) {
ss = br.readLine().split(" ");
for (int j = 1; j <= m; j++) {
int x = Integer.parseInt(ss[j - 1]);
add(i, j, i, j, x);
}
}
while (q-- > 0) {
ss = br.readLine().split(" ");
int x1 = Integer.parseInt(ss[0]), y1 = Integer.parseInt(ss[1]);
int x2 = Integer.parseInt(ss[2]), y2 = Integer.parseInt(ss[3]);
int v = Integer.parseInt(ss[4]);
add(x1, y1, x2, y2, v);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
bw.write(a[i][j] + " ");
}
bw.write("\n");
}
bw.close();
br.close();
}
static void add(int x1, int y1, int x2, int y2, int c) {
a[x1][y1] += c;
a[x1][y2 + 1] -= c;
a[x2 + 1][y1] -= c;
a[x2 + 1][y2 + 1] += c;
}
}
三维差分
其它例题
798. 得分最高的最小轮调
class Solution {
public int bestRotation(int[] nums) {
int n = nums.length;
int[] diff = new int[n + 1];
for (int i = 0; i < n; i++) {
if (nums[i] <= i) { // 第一类
diff[0] += 1;
diff[i - nums[i] + 1] -= 1;
diff[i + 1] += 1;
} else { //第二类
diff[0] += 0;
diff[i + 1] += 1;
diff[i + 1 + n - nums[i]] -= 1;
}
}
int minIdx = 0, score = 0;
int sum = 0;
for (int i = 0; i < n; i++) {
sum += diff[i];
if (sum > score) {
score = sum;
minIdx = i;
}
}
return minIdx;
}
}