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

前缀和与差分算法

一.前缀和

1.一维前缀和

给定一个数组arr,前缀和数组为sum,那么sum数组就是将从a【0】相加到a【i】的总和。

例如数组arr中有元素 1,2,3,4  那么前缀和数组则为1,1+2,1+2+3,1+2+3+4  。

有了前面的一些概念铺垫,那么前缀和有什么用呢?我们来看一个例题

题目描述

给定一个长度为n的数组a1,a2,....an

接下来有q次查询, 每次查询有两个参数l, r.

对于每个询问, 请输出al+al+1+....+ar

输入描述:

第一行包含两个整数n和q.

第二行包含n个整数, 表示a1,a2,....an

接下来q行,每行包含两个整数  l和r.

1≤n,q≤1e5
−1e9≤a[i]≤1d9
1≤l≤r≤n

输出描述:

输出q行,每行代表一次查询的结果.

 若每输入一组数我们就在遍历数组查找一次,那么算法的时间复杂度会变高。

所以我们可以在输入数组的时候事先对数组进行预处理,通过前缀和进行相加,当输入l和r时只需用前缀和相减就能得到范围的和,这样操作不需要遍历数组,那么时间复杂度就为 o(1)。

下面放上代码

#include <iostream>
using namespace std;

typedef long long LL;
LL ret = 0;
const int N = 1e5 + 10;
LL a[N];
LL f[N];

int main()
{
	int n, q; cin >> n >> q;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		f[i] = f[i - 1] + a[i];
	}
	while (q--)
	{
		int l, r; cin >> l >> r;
		ret = f[r] - f[l - 1];
		cout << ret << endl;
	}
	return 0;
}

 2.二维前缀和

二维前缀和与一维前缀和相似,二维前缀和建立在矩阵上。

我们来看上图,在矩阵上的前缀和和一维的有些许不一样,点(x1-1,y1-1)的前缀和为绿色的部分,点(x2,y2)前缀和为绿色红色黄色紫色部分的和。

下面我们来看一个题

题目描述

给你一个 n 行 m 列的矩阵 A ,下标从1开始。

接下来有 q 次查询,每次查询输入 4 个参数 x1 , y1 , x2 , y2

请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和,

输入描述:

第一行包含三个整数n,m,q.

接下来n行,每行m个整数,代表矩阵的元素

接下来q行,每行4个整数x1, y1, x2, y2,分别代表这次查询的参数

1≤n,m≤1000
1≤q≤1e5
−1e9≤a[i][j]≤1e9
1≤x1≤x2≤n
1≤y1≤y2≤m

输出描述:

输出q行,每行表示查询结果。

示例1

输入

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

输出

8
25
32

 首先我们输入数组并对数组进行预处理算出前缀和,若我们想计算上图(x2,y2)点的前缀和,可以用绿色+黄色(x2,y1-1前缀和)+绿色+红色(x1-1,y2前缀和)-绿色(重叠部分x1-1,y1-1前缀和)+紫色。

通过上面的算法得到前缀和最后进行还原数组

原理相同,可以理解为矩阵图形的加减,我们要求的就是一块图形,通过加减来得到。

下面放上代码

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 1e3 + 10;

LL a[N][N];
LL f[N][N];
LL ret;

int main()
{
	int n, m, q; cin >> n >> m >> q;
	for (int i = 1; i <= n; i++)//列
	{
		for (int j = 1; j <= m; j++)//行
		{
			cin >> a[i][j];
			f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + a[i][j];
		}
	}
	while (q--)
	{
		int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
		ret = f[x2][y2] - f[x2][y1 - 1] - f[x1 - 1][y2] + f[x1 - 1][y1 - 1];
		cout << ret << endl;
	}
	return 0;
}

3.总结

当我们需要求一段区间的和时,可以使用前缀和的方法,对数组进行预处理能降低查找的复杂度。

二.差分

1.一维差分

差分算法是一种处理数组区间修改问题的高效算法,通过构建差分数组来简化区间修改。例如数组arr中有1,2,3,4元素,那么差分数组可以写成1,1,1,1. 当我们要还原数组时1,1+1,1+1+1,1+1+1+1加上前项和就能还原出原数组。

下面我们来看一个例题

题目描述

对于给定的长度为 n 的数组 {a1,a2,…,an} ,我们有 m 次修改操作,每一次操作给出三个参数 l,r,k ,代表将数组中的 al,al+1,…,ar都加上 k 。

请你直接输出全部操作完成后的数组。

输入描述:

第一行输入两个整数 n,m(1≦n,m≦1e5) 代表数组中的元素数量、操作次数。

\第二行输入 n 个整数 a1,a2,…,an(−1e9≦ai≦1e9)) 代表初始数组。

此后 m 行,每行输入三个整数 l,r,k(1≦l≦r≦n; −1e9≦k≦1e9)代表一次操作。

输出描述:

在一行上输出 n 个整数,代表全部操作完成后的数组。

示例1

输入

3 2
1 2 3
1 2 4
3 3 -2

输出

5 6 1

我们要对l和r区间内的数组同时加上一个k,我们可以用差分的方法对数组进行预处理,当l和r区间内的数同时加上k后,l位置会比l-1位置大k,而r位置会比原来r+1位置差值少了k。因此我们只需要对l位置的差分数组加上k,r+1位置的差分数组减去k即可。

代码样例:

#include <iostream>
using namespace std;

const int N = 1e5 + 10;
typedef long long LL;
LL a[N];
LL f[N];

int main()
{
	int n, m; cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		f[i] = a[i] - a[i - 1];
	}
	while (m--)
	{
		int l, r, k; cin >> l >> r >> k;
		f[l] += k, f[r + 1] -= k;
	}
	LL ret = 0;
	for (int i = 1; i <= n; i++)
	{
		ret += f[i];
		cout << ret << " ";
	}
	return 0;
}

 2.二维差分

二维差分就是对一个特定的矩形内进行差分运算。

 如上图,若我们想要紫色的部分都加上一个k,该如何操作呢?

若我们对差分数组中点(x1,y1)加上一个k,那么这个点到右下角的整个区域相当于都加上了k,但我们只希望在紫色的部分加上k,那就需要减去多余的部分。我们对点(x1,y1)差分加上k,减去(x2+1,y1)的差分,减去(x1,y2+1)的差分,最后加上重叠被减去的部分(x2+1,y2+1)的差分。

下面来看一个例题

题目描述

给你一个n行m列的矩阵,下标从1开始。
接下来有q次操作,每次操作输入5个参数x1, y1, x2, y2, k
表示把以(x1, y1)为左上角,(x2,y2)为右下角的子矩阵的每个元素都加上k,
请输出操作后的矩阵。
 

输入描述:

第一行包含三个整数n,m,q.
接下来n行,每行m个整数,代表矩阵的元素

接下来q行,每行5个整数x1, y1, x2, y2, k,分别代表这次操作的参数

1≤n,m≤1000
1≤q≤1e5
1≤x1≤x2≤n
1≤y1≤y2≤m
−1e9≤矩阵中的元素≤1e9

输出描述:

输出n行,每行m个数,每个数用空格分开,表示这个矩阵。

示例1

输入

2 3 4
1 2 3
4 5 6
1 1 2 2 3
1 2 2 3 -1
1 1 1 3 4
1 1 2 1 1

输出

9 8 6
8 7 5

先对数组进行差分预处理,同时对矩阵范围进行加减,最后通过前缀和还原数组即可。 

代码样例:

#include <iostream>
using namespace std;

const int N = 1010;
typedef long long LL;
LL f[N][N];
LL a[N][N];

int main()
{
	int n, m, q; cin >> n >> m >> q;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> a[i][j];
			f[i][j] += a[i][j], f[i][j + 1] -= a[i][j];
			f[i + 1][j] -= a[i][j], f[i + 1][j + 1] += a[i][j];
		}
	}
	while (q--)//差分同加同减
	{
		LL x1, y1, x2, y2, k; cin >> x1 >> y1 >> x2 >> y2 >> k;
		f[x1][y1] += k, f[x1][y2 + 1] -= k, f[x2 + 1][y1] -= k, f[x2 + 1][y2 + 1] += k;
	}
	LL ret = 0;
	for (int i = 1; i <= n; i++)//还原数组
	{
		for (int j = 1; j <= m; j++)
		{
			f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];//还原数组
			cout << f[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

3.总结

当我们需要对一段区间进行同时加减时,可以使用差分算法进行预处理来降低复杂度。


http://www.kler.cn/a/567703.html

相关文章:

  • Jsonpath 使用说明
  • DockerでOracle Database 23ai FreeをセットアップしMAX_STRING_SIZEを拡張する手順
  • Grafana接入Zabbix数据源
  • 2025-03-01 学习记录--C/C++-PTA 7-35 有理数均值
  • 【AD】3-9 物料BOM表的设置与导出
  • js的数据代理机制
  • 一文速通C++非类型模板参数
  • Windows安装sql server2017
  • Arduino控制舵机
  • 前端Npm面试题及参考答案
  • SQL 中的 EXISTS 子句:探究其用途与应用
  • Yolo11实战:基于YOLOv11的半自动化数据标注技术实践
  • SCIKIT-LEARN 决策树实现csv文档简单的推论预测
  • C++:string类(简单介绍)
  • DaoCloud 亮相 2025 GDC丨开源赋能 AI 更多可能
  • 翻译: 深入分析LLMs like ChatGPT 一
  • MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享
  • 【STM32(HAL库) RTC】(实时时钟)配置
  • 【力扣】2619. 数组原型对象的最后一个元素——认识原型与原型链
  • java23种设计模式-观察者模式