一维差分
问题描述
给定一个长度为 nn 的序列 aa。
再给定 mm 组操作,每次操作给定 33 个正整数 l,r,dl,r,d,表示对 al∼ral∼r 中的所有数增加 dd。
最终输出操作结束后的序列 aa。
Update:由于评测机过快,n,mn,m 于 2024-12-09 从 105105 加强至 2×1052×105,杜绝暴力通过本题。
输入格式
第一行输入两个正整数 n,mn,m。(1≤n,m≤2×1051≤n,m≤2×105)
第二行输入 nn 个正整数 aiai。(1≤i≤n,1≤ai≤1041≤i≤n,1≤ai≤104)。
接下来 mm 行,每行输入 33 个正整数 l,r,dl,r,d。(1≤l≤r≤n,−104≤d≤1041≤l≤r≤n,−104≤d≤104)。
输出格式
输出 nn 个整数,表示操作结束后的序列 aa。
样例输入
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
样例输出
3 4 5 3 4 2
思路解析
1. 输入数据
-
读取数组的长度 n 和操作的次数 m。
-
创建两个数组:
-
a
:存储原始数组的值。 -
sum
:差分数组,用于记录每个位置的变化。
-
2. 构建差分数组
-
遍历数组
sum[i]=a[i]−a[i−1]a
,计算差分数组sum
:-
解释:
-
差分数组
sum
的每个位置存储了当前元素与前一个元素的差值。 -
通过差分数组,可以高效地对区间进行加法操作。
-
-
3. 处理区间加法操作
-
对于每次操作,指定区间 [l,r] 和值 tmp:
-
在差分数组
sum
中:-
sum[l] += \text{tmp}
:表示从位置 l 开始的区间增加 tmp。 -
sum[r+1] -= \text{tmp}
:表示在位置 r+1 之后的区间减去 tmp,以抵消前面的增加操作。
-
-
注意:如果 r+1 超出数组范围,则不需要减去 tmp。
-
4. 恢复原始数组
-
遍历差分数组
a[i]=a[i−1]+sum[i]sum
,恢复更新后的数组a
:-
解释:
-
通过累加差分数组的值,可以恢复每个位置的最终值。
-
-
5. 输出结果
-
输出更新后的数组
a
代码实现
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan = new Scanner(System.in);// 读取数组长度和操作次数int n = scan.nextInt();int m = scan.nextInt();// 创建数组存储原始数组和差分数组int[] a = new int[n + 1];int[] sum = new int[n + 1];// 输入数组并构建差分数组for (int i = 1; i <= n; i++) {a[i] = scan.nextInt();sum[i] = a[i] - a[i - 1];}// 处理区间加法操作for (int i = 1; i <= m; i++) {int l = scan.nextInt();int r = scan.nextInt();int tmp = scan.nextInt();// 对区间 [l, r] 进行更新sum[l] += tmp;if (r + 1 <= n) {sum[r + 1] -= tmp;}}// 恢复更新后的数组for (int i = 1; i <= n; i++) {a[i] = a[i - 1] + sum[i];System.out.print(a[i] + " ");}scan.close();}
}
总结
-
核心思路:
-
利用差分数组高效地处理区间加法操作。
-
通过差分数组的累加恢复最终的数组。
-
-
关键步骤:
-
构建差分数组:
sum[i]=a[i]−a[i−1] -
处理区间加法:
sum[l]+=tmpsum[r+1]−=tmp(如果 r+1≤n) -
恢复数组:
a[i]=a[i−1]+sum[i]
-
-
优点:
-
时间复杂度:每次区间加法操作的时间复杂度为 O(1),恢复数组的时间复杂度为 O(n)。
-
空间复杂度:额外空间复杂度为 O(n)。
-
二维差分
问题描述
给定一个 n×mn×m 大小的矩阵 AA。
给定 qq 组操作,每次操作为给定 55 个正整数 x1,y1,x2,y2,dx1,y1,x2,y2,d,Ax1,y1Ax1,y1 是子矩阵左上角端点,Ax2,y2Ax2,y2 是子矩阵右下角端点,你需要给其中每个元素都增加 dd。
输出操作结束后的矩阵 AA。
输入格式
第一行输入 33 个正整数 n,m,qn,m,q。(1≤n,m≤103,1≤q≤1051≤n,m≤103,1≤q≤105)
接下来 nn 行每行输入 mm 个整数,表示 Ai,jAi,j。(−103≤Ai,j≤103,1≤i≤n,1≤j≤m)(−103≤Ai,j≤103,1≤i≤n,1≤j≤m)
接下来 qq 行,每行输入 55 个正整数 x1,y1,x2,y2,dx1,y1,x2,y2,d。(1≤x1≤x2≤n,1≤y1≤y2≤m,−103≤d≤103)(1≤x1≤x2≤n,1≤y1≤y2≤m,−103≤d≤103)
输出格式
输出 nn 行 mm 个整数,表示操作结束后的矩阵 AA。
样例输入
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
思路解析
1. 输入数据
-
读取矩阵的行数 n 和列数 m,以及操作的次数 q。
-
创建两个二维数组:
-
a
:存储原始矩阵的值。 -
sum
:差分数组,用于记录每个位置的变化。
-
2. 构建差分数组
-
遍历矩阵
sum[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1]a
,计算差分数组sum
:-
解释:
-
差分数组
sum
的每个位置存储了当前元素与相邻元素的差值。 -
通过差分数组,可以高效地对区间进行加法操作。
-
-
3. 处理区间加法操作
-
对于每次操作,指定区间 [x1,y1] 到 [x2,y2] 和值 tmp:
-
在差分数组
sum
中:-
sum[x1][y1] += \text{tmp}
:表示从位置 [x1,y1] 开始的区间增加 tmp。 -
sum[x2+1][y1] -= \text{tmp}
:表示在位置 [x2+1,y1] 之后的区间减去 tmp,以抵消前面的增加操作。 -
sum[x1][y2+1] -= \text{tmp}
:表示在位置 [x1,y2+1] 之后的区间减去 tmp,以抵消前面的增加操作。 -
sum[x2+1][y2+1] += \text{tmp}
:表示在位置 [x2+1,y2+1] 之后的区间加上 tmp,以抵消前面的减法操作。
-
-
注意:如果某个位置超出矩阵范围,则不需要进行操作。
-
4. 恢复原始矩阵
-
遍历差分数组
a[i][j]=a[i−1][j]+a[i][j−1]−a[i−1][j−1]+sum[i][j]sum
,恢复更新后的矩阵a
:-
解释:
-
通过累加差分数组的值,可以恢复每个位置的最终值。
-
-
5. 输出结果
-
输出更新后的矩阵
a
。
代码实现
import java.util.*;public class Main {static final int N = 1000 + 10;static int[][] a = new int[N][N];static int[][] sum = new int[N][N];// 差分操作函数public static void f(int x1, int y1, int x2, int y2, int tmp) {sum[x1][y1] += tmp;sum[x2 + 1][y1] -= tmp;sum[x1][y2 + 1] -= tmp;sum[x2 + 1][y2 + 1] += tmp;}public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 读取矩阵的行数、列数和操作次数int n = sc.nextInt();int m = sc.nextInt();int q = sc.nextInt();// 输入矩阵并构建差分数组for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {a[i][j] = sc.nextInt();sum[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];}}// 处理区间加法操作while (q-- > 0) {int x1 = sc.nextInt();int y1 = sc.nextInt();int x2 = sc.nextInt();int y2 = sc.nextInt();int tmp = sc.nextInt();f(x1, y1, x2, y2, tmp);}// 恢复矩阵并输出结果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] + sum[i][j];System.out.print(a[i][j] + " ");}System.out.println();}sc.close();}
}
总结
-
核心思路:
-
利用二维差分数组高效地处理区间加法操作。
-
通过差分数组的累加恢复最终的矩阵。
-
-
关键步骤:
-
构建差分数组:
sum[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1] -
处理区间加法:
sum[x1][y1]+=tmpsum[x2+1][y1]−=tmpsum[x1][y2+1]−=tmpsum[x2+1][y2+1]+=tmp -
恢复矩阵:
a[i][j]=a[i−1][j]+a[i][j−1]−a[i−1][j−1]+sum[i][j]
-
-
优点:
-
时间复杂度:每次区间加法操作的时间复杂度为 O(1),恢复矩阵的时间复杂度为 O(n×m)。
-
空间复杂度:额外空间复杂度为 O(n×m)。
-
相关的习题练习
棋盘
问题描述
小蓝拥有 n×nn×n 大小的棋盘,一开始棋盘上全都是白子。小蓝进行了 mm 次操作,每次操作会将棋盘上某个范围内的所有棋子的颜色取反(也就是白色棋子变为黑色,黑色棋子变为白色)。请输出所有操作做完后棋盘上每个棋子的颜色。
输入格式
输入的第一行包含两个整数 nn,mm,用一个空格分隔,表示棋盘大小与操作数。
接下来 mm 行每行包含四个整数 x1x1,y1y1,x2x2,y2y2,相邻整数之间使用一个空格分隔,表示将在 x1x1 至 x2x2 行和 y1y1 至 y2y2 列中的棋子颜色取反。
输出格式
输出 nn 行,每行 nn 个 00 或 11 表示该位置棋子的颜色。如果是白色则输出 00,否则输出 11。
样例输入
3 3
1 1 2 2
2 2 3 3
1 1 3 3
样例输出
001
010
100
评测用例规模与约定
对于 3030% 的评测用例,n,m≤500n,m≤500 ;
对于所有评测用例,1≤n,m≤20001≤n,m≤2000,1≤x1≤x2≤n1≤x1≤x2≤n,1≤y1≤y2≤m1≤y1≤y2≤m。
思路解析
1. 输入数据
-
读取棋盘的大小 n 和操作的次数 m。
-
创建两个二维数组:
-
b
:二维差分数组,用于记录每次翻转操作的影响。 -
s
:二维前缀和数组,用于计算每个位置的翻转次数。
-
2. 处理翻转操作
-
对于每次操作,指定矩形区域 [x1,y1] 到 [x2,y2]:
-
在差分数组
b
中:-
b[x1][y1] += 1
:表示从位置 [x1,y1] 开始的区域增加一次翻转。 -
b[x2 + 1][y1] -= 1
:表示在位置 [x2+1,y1] 之后的区域减去一次翻转,以抵消前面的增加操作。 -
b[x1][y2 + 1] -= 1
:表示在位置 [x1,y2+1] 之后的区域减去一次翻转,以抵消前面的增加操作。 -
b[x2 + 1][y2 + 1] += 1
:表示在位置 [x2+1,y2+1] 之后的区域加上一次翻转,以抵消前面的减法操作。
-
-
注意:如果某个位置超出棋盘范围,则不需要进行操作。
-
3. 计算二维前缀和
-
遍历差分数组
s[i][j]=b[i][j]+s[i−1][j]+s[i][j−1]−s[i−1][j−1]b
,计算每个位置的翻转次数:-
解释:
-
通过累加差分数组的值,可以计算每个位置的翻转次数。
-
-
4. 确定最终状态
-
遍历二维前缀和数组
s
,判断每个位置的翻转次数的奇偶性:-
如果翻转次数为奇数,表示该位置的颜色被翻转了奇数次,最终状态为白色(假设初始状态为黑色)。
-
如果翻转次数为偶数,表示该位置的颜色被翻转了偶数次,最终状态为黑色。
-
输出每个位置的最终状态。
-
代码实现
import java.util.Scanner;public class Main {static final int N = 2000 + 10; // 假设最大值为2000,加上一些缓冲static int[][] b = new int[N][N]; // 二维差分数组,用于记录翻转操作的影响static int[][] s = new int[N][N]; // 二维前缀和数组,用于计算每个位置的翻转次数static int n, m;public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 读取输入n = sc.nextInt(); // 棋盘大小m = sc.nextInt(); // 操作次数// 处理每个翻转操作while (m-- > 0) {int x1 = sc.nextInt();int y1 = sc.nextInt();int x2 = sc.nextInt();int y2 = sc.nextInt();// 更新二维差分数组 bb[x1][y1] += 1;if (x2 + 1 < N) b[x2 + 1][y1] -= 1;if (y2 + 1 < N) b[x1][y2 + 1] -= 1;if (x2 + 1 < N && y2 + 1 < N) b[x2 + 1][y2 + 1] += 1;}// 计算二维前缀和并确定最终状态for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {// 计算二维前缀和s[i][j] = b[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];// 判断翻转次数的奇偶性,确定最终颜色System.out.print(s[i][j] % 2);}System.out.println(); // 换行}sc.close(); // 关闭输入流}
}
总结
-
核心思路:
-
利用二维差分数组高效地处理区间翻转操作。
-
通过二维前缀和数组计算每个位置的翻转次数,判断奇偶性确定最终状态。
-
-
关键步骤:
-
处理翻转操作:
b[x1][y1]+=1b[x2+1][y1]−=1b[x1][y2+1]−=1b[x2+1][y2+1]+=1 -
计算二维前缀和:
s[i][j]=b[i][j]+s[i−1][j]+s[i][j−1]−s[i−1][j−1] -
确定最终状态:
最终状态=s[i][j]%2
-
-
优点:
-
时间复杂度:每次翻转操作的时间复杂度为 O(1),计算前缀和的时间复杂度为 O(n2)。
-
空间复杂度:额外空间复杂度为 O(n2)。
-
自学蓝桥杯笔记,希望我们可以一起学习!