【算法笔记】前缀和经典题目解析

前缀和可以用于快速求某一个区间各个值的和,原理是先预处理好所有的值,创建一个预处理好的数组,这个预处理的数组中的每个值都是原数组相同位置上前面的所有值相加。这是一种空间换取时间的做法。


1.一维前缀和

1.1【模板】前缀和

假设数组 f[N] 为我们的预处理数组,a[N] 为我们的原数组, 那创建预数组的方式为:

cpp 复制代码
f[i] = f[i - 1] + a[i];

有时候我们并不一定要保留原来值,只要能创建出预数组就可以;

想要还原出区别和也非常简单,假设 l 为左区间, r 为又区间,那这个区别的和为:

cpp 复制代码
f[r] - f[l - 1]

这就是前缀和的核心代码,这个算法主要是和别的算法一起搭在一起考察的:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n, m;
LL f[N];

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

1.2最大子段和

这道题也可以使用前缀和来解决,因为我们要想找出最大的区间,我们是想要让减去的后面那段尽可能的小,所以我们可以定义一个变量premin使得减去前面的区间最小:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int f[N];
int n;

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int x; cin >> x;
		f[i] = f[i - 1] + x;
	}
	int ret = -1e5;
	int premin = 0;
	for (int i = 1; i <= n; i++)
	{
		ret = max(ret, f[i] - premin);
		premin = min(premin, f[i]);
	}
	cout << ret << endl;
	return 0;
}

2.二维前缀和

2.1【模板】二维前缀和

二维前缀和与一维前缀和行驶的功能是完全一样的,只不过将一段扩展成了一个二维空间。

二维前缀和的预处理和还原方式与一维有些不同,但我们也是可以推出来的

我们设垂直方向为i,水平方向为j,可以得到:

更大的区别正是由这样一个个的子问题推导出来的,所以二维前缀和的公式可以为:

cpp 复制代码
f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + x

还原的推理公式也是同理我这里就不再赘述了

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1010;
typedef long long LL;
int n, m, q;
LL f[N][N];
LL a[N][N];

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

2.2激光炸弹

这道题是二维前缀和的经典运用,我们可以通过m计算出这个区间,注意边界问题即可:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 5010;
int a[N][N];
int f[N][N];
int n, m;

int main()
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		int x, y, v; cin >> x >> y >> v;
		x++; y++;
		a[x][y] += v;
	}
	for (int i = 1; i < N; i++)
	{
		for (int j = 1; j < N; j++)
		{
			f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + a[i][j]; 
		}
	}
	int ret = 0;
	m = min(m, N);
	for (int i = m; i < N; i++)
	{
		for (int j = m; j < N; j++)
		{
			int x2, y2; x2 = i; y2 = j;
			int x1, y1; x1 = i - m + 1; y1 = j - m + 1;
			int t = f[x2][y2] - f[x2][y1 - 1] - f[x1 - 1][y2] + f[x1 - 1][y1 - 1];
			ret = max(ret, t);
		}
	}
	cout << ret << endl;
	return 0;
}

相关推荐
橙子也要努力变强1 小时前
信号捕捉与不可捕捉机制(进阶篇)
linux·服务器·c++
Je1lyfish1 小时前
Haskell 初探
开发语言·笔记·算法·rust·lisp·抽象代数
橙子也要努力变强2 小时前
信号的处理方式与生命周期(核心机制篇)
linux·网络·c++
Aliex_git2 小时前
前端监控笔记(三)
前端·笔记·学习
zzb15802 小时前
Kotlin 密封类与延迟初始化学习笔记
笔记·学习·kotlin
特种加菲猫2 小时前
C++ 容器适配器揭秘:stack, queue 和 priority_queue 的模拟实现
开发语言·c++
im_AMBER2 小时前
Leetcode 159 无重复字符的最长子串 | 长度最小的子数组
javascript·数据结构·学习·算法·leetcode
三品吉他手会点灯2 小时前
C语言学习笔记 - 2.C概述 - HelloWorld程序举例
c语言·笔记·学习
Felven2 小时前
D. Zero Remainder Array
c语言