一、基本思路
1、一维差分算法
- 原序列:a1,a2,a3,……,an
- 差分构造:b1,b2,b3,……,bn
- 两者联系:
- 原序列是差分序列的前缀和,也就是ai = b1 + b2 + …+bi
- 如何求解bn:
- 先假设bn已知,至于后续如何处理,看第三部分即可了解。
- 目标操作:
- 将原序列 an 的索引【L,R】内加上常数
- 等价为:
- a[L] + C, a[L + 1] + C,…,a[R] + C
- 解决办法:
- 暴力求解:直接进行循环求解,复杂度O(n)
- 差分求解:因为只需修改 b[L]、b[R + 1] 两部分,复杂度 O(1)
差分求解具体步骤 :
- 首先需要明确 an 为bn 前缀和
- 当 b[L] + C ,则 a[L] ~ a[N] 相应全部 +C
- 若在 R 后的 an 不再 + C,则 b[R + 1] - C
b[L] += C
b[R + 1] -= C
2、二维差分算法
- 原矩阵 a[ i ][ j ]
- 差分矩阵: b[ i ][ j ]
- 两者联系:
- a[i][j] 是 b[i][j] 的前缀和,其实就是b矩阵左上角的和
- 问题: 求解 a 矩阵中蓝色方块 + C 后的结果
- 操作:对 b[i][j] + C 的效果为右下角的 a[i][j]全部 + C
- 差分解法——使用 b[i][j] 进行求解
- b[x1][y1] += c;
- b[x2 + 1][y1] -= c;
- b[x1][y2 + 1] -= c;
- b[x2 + 1][y2 + 1] += c;
3、差分序列 bn 的构造理解
- 假定原序列 an 的初始值为 0,0,……,0,共有n个,则 bn 也为0
- 经过 n 次 insert(i, i, a[i]) 对bn的操作得到差分序列——bn
- 无需考虑如何构造bn,只需考虑更新即可
原理理解:
二、Java、C语言模板实现
//Java 模板实现
// 一维差分代码
import java.util.Scanner;
public class Main {
private static final int N = 100010;
private static final int[] an = new int[N];
private static final int[] bn = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
for (int i = 1; i <= n ; i++){
an[i] = scanner.nextInt(); // an 输入
}
for (int i = 1; i <= n; i++){
insertDifference(i, i, an[i]); // bn 初始化
}
for (int i = 0; i < m; i++){
int left = scanner.nextInt();
int right = scanner.nextInt();
int add = scanner.nextInt();
insertDifference(left, right, add); // 插入
}
for (int i = 1; i <= n; i++){
an[i] = an[i - 1] + bn[i];
System.out.print(an[i] + " ");
}
}
private static void insertDifference(int left, int right, int add){ // 一维差分核心代码
bn[left] += add;
bn[right + 1] -= add;
}
}
// 二维差分
import java.util.Scanner;
public class Main {
private static final int N = 1010;
private static final int[][] an = new int[N][N];
private static final int[][] bn = new int[N][N];
private static void insertDifference2D(int x1, int y1, int x2, int y2, int add){ // 二维差分核心代码
bn[x1][y1] += add;
bn[x2 + 1][y1] -= add;
bn[x1][y2 + 1] -= add;
bn[x2 + 1][y2 + 1] += add;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int q = scanner.nextInt();
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
an[i][j] = scanner.nextInt(); // an 初始化
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
insertDifference2D(i, j, i, j, an[i][j]); // bn 初始化
}
}
for (int i = 0; i < q; i++) {
int x1 = scanner.nextInt();
int y1 = scanner.nextInt();
int x2 = scanner.nextInt();
int y2 = scanner.nextInt();
int add = scanner.nextInt();
insertDifference2D(x1, y1, x2, y2, add);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
an[i][j] = an[i - 1][j] + an[i][j - 1] - an[i - 1][j - 1] + bn[i][j]; // an重新进行求和处理,是上一节的前缀和内容
System.out.print(an[i][j] + " ");
}
System.out.println("");
}
}
}
//
// C语言实现,此处是yxc实现
// 一维差分
给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
// 二维差分
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
三、注意事项
- 初始化 an 与 bn 序列为0 ,一定要记住,记得全局变量和局部变量的关系
- 对于 bn 的构造,要记得自己画一下,才能发现其中只需要考虑更新an、bn的操作;此处的假设an初始为0,然后一步步构造的思想太妙了,一步步利用an值,原地插入得到bn,这背后是有数学原理的,可以看看数列部分。
- 容斥原理:注意边界问题