当前位置: 首页 > news >正文

【AcWing】前缀和与差分(一维 + 二维)

前缀和与差分(一维 + 二维)

这一部分内容来自于AcWing基础算法课的前缀和与差分部分,主要包含四道前缀和与差分的例题。

前缀和可以在 O ( 1 ) O(1) O(1)的时间复杂度内进行区间求和,而差分可以在 O ( 1 ) O(1) O(1)的时间复杂度内完成区间内值的修改。

一维前缀和

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;const int maxn = 1e5 + 5;
int n, m;
int q[maxn] = {0};
int p[maxn] = {0};int main()
{ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;for(int i=1;i<=n;i++){cin >> q[i];p[i] = p[i-1] + q[i];   // 在输入q的过程中直接对前缀和p进行维护}// 1 2 3 4 5// 1 3 6 10 15while(m--){int l, r;cin >> l >> r;cout << p[r] - p[l-1] << endl;}return 0;
}

二维前缀和

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;const int maxn = 1e3 + 5;
int n, m, q;
int a[maxn][maxn] = {0};
int b[maxn][maxn] = {0};int main()
{ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m >> q;for(int i=1; i<=n; i++) {for(int j=1; j<=m; j++){cin >> a[i][j];b[i][j] = b[i-1][j] + b[i][j-1] - b[i-1][j-1] + a[i][j];}}while(q--){int x1, y1, x2, y2;cin >> x1 >> y1 >> x2 >> y2;cout << b[x2][y2] - b[x1-1][y2] - b[x2][y1-1] + b[x1-1][y1-1] << endl;}return 0;
}

一维差分

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;const int maxn = 1e5 + 5;
int n, m;
int q[maxn] = {0}, p[maxn] = {0};int main()
{ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;for(int i=1;i<=n;i++){cin >> q[i];p[i] = q[i] - q[i-1];}// 1 2 3 4 5 -> 1 2 13 14 5// 1 1 1 1 1 -> 1 1 11 1 -9while(m--) {int l, r, k;cin >> l >> r >> k;p[l] += k;p[r+1] -= k;}for(int i=1;i<=n;i++){p[i] += p[i-1];cout << p[i] << " ";}return 0;
}

二维差分

其中,二维差分最难理解,在本科阶段的程序设计竞赛当中,我的印象里是肯定遇到过二维差分的模板题的,并且那道题应该是被算作了一道较难的题目,因此对二维差分进行完全理解是很有意义的。

二维差分矩阵的作用是快速地对二维矩阵从 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2)的值进行修改,使这个区间内的值加上 c c c。显然如果直接使用暴力求解的话,时间复杂度将来到 O ( n 2 ) O(n^2) O(n2),而如果在处理输入的同时维护一个二维差分矩阵,则可以将时间复杂度降低到 O ( 1 ) O(1) O(1)

这里理解差分的一个关键点是,原始的输入矩阵是其差分矩阵的前缀和矩阵,如何理解这句话?我们可以先看一个一维的例子:

假定原始矩阵为:

1 2 3 4 5 6 7 8 9 10

其差分矩阵必然是:

1 1 1 1 1 1 1 1 1 1

显然对上述矩阵求前缀和即可恢复出原始矩阵。再看一个更复杂的例子:

假定原始矩阵为:

3 6 9 12 4 -7

其差分矩阵为:

3 3 3 3 -8 -11

不难看出原始矩阵仍然是差分矩阵的前缀和矩阵。

下面来看二维的情况,首先需要理清楚的是,二维矩阵的前缀和代表的是某个位置 ( x , y ) (x, y) (x,y) ( 1 , 1 ) (1, 1) (1,1)之间矩阵元素值之和。因此如果原始矩阵为:

1 2 3 4
5 6 7 8
9 0 1 2
3 4 5 6

差分矩阵的构造仅考虑其相邻的元素,假定当前位置的下标为 ( i , j ) (i, j) (i,j),差分矩阵记为 b b b,原矩阵记为 A A A,那么该位置的差分值就是: b [ i ] [ j ] = a [ i ] [ j ] − a [ i ] [ j − 1 ] − a [ i − 1 ] [ j ] + a [ i − 1 ] [ j − 1 ] b[i][j] = a[i][j] - a[i][j-1] - a[i-1][j] + a[i-1][j-1] b[i][j]=a[i][j]a[i][j1]a[i1][j]+a[i1][j1]

上述矩阵的差分矩阵如下,可以自行验证上述矩阵是下述差分矩阵的前缀和矩阵。

 1  1  1  14  0  0  04 -10 0  0
-6  10 0  0 

0 0 0为起始下标,如果想要修改 ( 1 , 1 ) (1, 1) (1,1) ( 2 , 2 ) (2, 2) (2,2),使它们都加上 1 1 1,在原始矩阵上直接进行修改的时间复杂度是 O ( n 2 ) O(n^2) O(n2),直接使用暴力法对相应下标进行遍历即可。但我们可以直接在差分矩阵上进行修改,从而将时间复杂度降低到 O ( 1 ) O(1) O(1)

修改后的矩阵变为:

1 2 3 4
5 7 8 8
9 1 2 2
3 4 5 6

其差分矩阵为:

 1  1  1  14  1  0 -14 -10 0  0
-6  9  0  1

根据差分矩阵的变化,可以推导出二维差分的区间修改公式:

假定要对二维区间 a a a进行修改,使得 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2)之间的数加上 c c c,那么可以在 O ( 1 ) O(1) O(1)内对差分矩阵 b b b的以下位置进行如下修改:

b[x1][y1] += c;
b[x1][y2+1] -= c;
b[x2+1][y1] -= c;
b[x2+1][y2+1] += c;

在最后输出时,直接对上述矩阵 b b b求二维前缀和即可:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;const int maxn = 1e3 + 5;
int n, m ,q;int b[maxn][maxn] = {0};void insert(int x1, int y1, int x2, int y2, int c) { // 将二维差分的维护抽象为insert函数b[x1][y1] += c;b[x1][y2+1] -= c;b[x2+1][y1] -= c;b[x2+1][y2+1] += c;
}int main()
{ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m >> q;for(int i=1; i<=n; i++) {for(int j=1; j<=m; j++){int x;cin >> x;insert(i, j, i, j, x);	// 注意这里是一个trick, 相当于直接对全0矩阵进行二分差分的维护// 直接对维护过后的二维矩阵求前缀和即可得到最开始的输入矩阵, 这样避免了在最开始输入矩阵之// 后重复对矩阵求一次二维差分的过程}}while(q--) {int x1, y1, x2, y2, x;cin >> x1 >> y1 >> x2 >> y2 >> x;insert(x1, y1, x2, y2, x);}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1]; // 对二维矩阵求前缀和得到最终结果cout << b[i][j] << " ";}cout << endl;}return 0;
}

http://www.mrgr.cn/news/28825.html

相关文章:

  • Linux kernel 堆溢出利用方法(二)
  • 力扣每日一题 3261. 统计满足 K 约束的子字符串数量 II
  • 前端基础的讲解-JS(10)
  • 【再谈设计模式】建造者模式~对象构建的指挥家
  • scala的练习题
  • 深入理解接口测试:实用指南与最佳实践5.0(一)
  • 企业级即时通讯平台有哪些?探究适合企业使用的即时通讯工具
  • 72、结合无人机进行rk3588oak-lite跟踪目标物体进行识别、跟踪、保持距离
  • 虚拟机centos_7 配置教程(镜像源、配置centos、静态ip地址、Finalshell远程操控使用)
  • LeetCode 每日一题 2024/9/9-2024/9/15
  • 微服务下设计一个注解标识是否需要登录
  • ccfcsp-202203(1、2)
  • LaTex2024 下载安装运行HelloWorld—全流程笔记
  • 动手学深度学习(四)卷积神经网络-下
  • 数据结构易错整理1
  • C++基础知识7 list
  • 变压器漏感对整流电路的影响
  • C++学习笔记(28)
  • 进程间关系与进程守护
  • ZooKeeper远程连接超时排查与解决
  • 如何用安卓玩Java版Minecraft,安卓手机安装我的世界Java版游戏的教程
  • 过拟合与欠拟合、批量标准化
  • docker- No space left on device
  • 开源模型应用落地-qwen模型小试-调用Qwen2-VL-7B-Instruct-更清晰地看世界(一)
  • 紧急预警!台风贝碧嘉正面袭击上海浦东,风雨交加影响全城
  • 自然语言处理实战项目