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

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


1.一维前缀和

1.1【模板】前缀和

假设数组 fN 为我们的预处理数组,aN 为我们的原数组, 那创建预数组的方式为:

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;
}

完

相关推荐
piikee34 分钟前
粤语初学入门:从粤拼、九声六调到开口,一个普通话母语者的练习笔记
笔记·粤语入门·怎么学习粤语
by2099937 分钟前
vector 的模拟实现:扩容与边界的详细阐述(下)
c++·经验分享·vector·动态数组
sunshine22 girl40 分钟前
Java学习七 Java 项目实战1-项目创建和主页面搭建
java·开发语言·学习
传奇开心果编程2 小时前
【SwiftUI提高练中学】第8课 网络层架构:拦截器、重试、缓存与离线优先
学习·macos·ui·ios·swiftui
一条破秋裤2 小时前
内核驱动的意义与课程学习规划
学习
念何架构之路2 小时前
zap采样器与性能优化内幕
算法·性能优化·哈希算法
Bug收容所2 小时前
学习LangChain day1
学习·langchain·llm·agent
传奇开心果编程2 小时前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
晒太羊的猫3 小时前
hot100动态规划刷题
算法·动态规划
Seraphina363 小时前
SQL注入(二)盲注
网络·数据库·经验分享·笔记·sql