二维差分
食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目只有题目分析,代码实现,代码误区
题目描述:
-
输入一个 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 -
题目来源:https://www.acwing.com/problem/content/800/
题目分析:
- 给定一个矩形,多次统一修改内部子矩形,只要求输出最终矩形
- 暴力:
对q次修改,每次从(x1, y1)遍历到(x2, y2),对子矩形内部所有点进行加减c
最坏时间复杂度:O(qmn),一千*一千*十万 = 千亿ms,等于十个拼多多补贴,超时严重 - 已经不难想到了,对于大范围的修改,一般使用求差分,改差分点,还原回前缀和的套路
将O(qmn)的时间复杂度降低到O(mn),每次改动二维差分O(4),求二维差分O(mn),还原前缀和O(mn)
复杂度最坏一千*一千 = 一百万ms,可
算法原理:
含义:
- 一维差分对应修改一维前缀和,通过修改一个区间两个端点的差分数组值来修改这个区间内部整体元素的和
- 二维差分对应修改二维前缀和,通过修改一个矩形四个顶点的差分数组值来修改这个矩形内部整体元素的和
- 同于一维差分,二维差分的定义为:
给定一个矩形的前缀和数组s[n][m],还原回原本的数组arr[n][m]
arr[n][m]称为二维前缀和数组s[n][m]的二维差分数组
作用:
-
修改二维差分数组中一点造成二维前缀和数组中一个矩形内所有点的修改
如:修改差分矩形中(4,4)一点,造成前缀和矩形中以(4,4)为左上顶点,(n,m)为右下顶点的子矩形内部所有点的修改
-
修改一个点造成右下子矩形发生变化,那么我们可以通过修改4个点来指定一个子矩形发生变化,这就是二维差分矩形波动公式,但是在这之前我们需要先求得二维差分数组
二维差分修改矩形公式:
- 欲为(x1, y1)为左上顶点,(x2, y2)为右下顶点的矩阵内所有元素+c
只需修改差分矩形中四个点:(x1, y1),(x2+1, y1),(x1, y2+1),(x2+1, y2+1) - arr[x1][y1]+c效果:
- arr[x2+1][y1]-c效果:
- arr[x1][y2+1]-c效果:
- arr[x2+1][y2+1]+c效果:在这里插入图片描述
- 二维差分修改矩形公式:
arr[x1][y1]+c
arr[x1][y2+1]-c
arr[x2+1][y1]-c
arr[x2+1][y2+1]+c
求二维差分公式:
法一:打表
- 回顾二维前缀和打表公式:
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j];
所以
arr[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1];
法二:插入
- 假设原本的s[][] 和 arr[][]都是0
现在向s[2][2]矩形内点都+1
相当于arr[0][0]+1,arr[3][0]-1,arr[0][3]-1,arr[3][3]+1 - 每给出s[i][j]=c一点
需要修改arr[0][0]+c,arr[i+1][0]-c,arr[0][j+1]-c,arr[i+1][j+1]+c四点
存储形式:
- 二维差分和二维前缀和数组都以简单二维数组存储即可
- 从二维数组的arr[1][1] 和 s[1][1]开始存储
写作步骤:
3步:
1. 求出二维差分数组
- 插入法 / 二维前缀和公式
2. 修改差分数组中四点
- (x1, y2)+c, (x1, y2+1)-c,(x2+1, y1)-c,(x2+1, y2+1)+c
3. 还原回二维前缀和数组
- 二维前缀和公式:
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j]; - dp:计算arr[i][j]时,已经计算完成了arr[i-1][j],arr[i][j-1],arr[i-1][j-1]三者,即这三者已代表三个矩形和
arr[i][j] += arr[i-1][j] + arr[i][j-1] - arr[i-1][j-1];
代码模板:
#include <iostream>
using namespace std;
const int N = 1010;
int s[N][N];
int arr[N][N];
void insert(int x1, int y1, int x2, int y2, int c){
arr[x1][y1] += c;
arr[x1][y2+1] -= c;
arr[x2+1][y1] -= c;
arr[x2+1][y2+1] += c;
}
int main(){
int n = 0, m = 0, q = 0;
cin >>n >>m >>q;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin >>s[i][j];
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
//逆推前缀和公式:arr[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1];
insert(i,j,i,j,s[i][j]);
for(int i=1; i<=q; i++){
int x1 = 0, y1 = 0, x2 = 0, y2 = 0, c = 0;
cin >>x1 >>y1 >>x2 >>y2 >>c;
insert(x1, y1, x2, y2, c);
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
//也可以直接使用差分数组记录前缀和数组 arr[i][j] += arr[i-1][j] + arr[i][j-1] - arr[i-1][j-1]
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j];
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++)
cout<<s[i][j] <<" ";
cout<<endl;
}
return 0;
}
代码误区:
1. 疑问?
2. 致命问题:
- 原因/可能性分析:
- 解决方案:
本篇感想:
- 这一片写下来,我感觉我应该忘不了二维差分的四个公式了
- 差分修改公式:
arr[x1][y1] += c;
arr[x2+1][y1] -= c;
arr[x1][y2+1] -= c;
arr[x2+1][x2+1] += c; - 求差分公式:
- arr[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1];
- insert(i, j, i, j, s[i][j]);
- 差分还原前缀和公式:
- arr[i][j] += arr[i-1][j] + arr[i][j-1] - arr[i-1][j-1];
- s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j];
- 看完本篇博客,恭喜已登《练气境-初期》,再向后写四五篇就是中期,加油
距离登仙境不远了,加油