基础算法 —— 前缀和 【复习总结】
1. 简介
前缀和是经典的用空间换时间的方法,它的核心思想是预处理,可以在枚举过程中,快速给出查询结果。后文我们会遇到一维前缀和与二维前缀和。
它们的处理方法都可以分为两步:1. 创建前缀和数组;2. 查询目标区间
2. 一维前缀和
2.1 模板
1. 套用公式创建前缀和数组:f[i] = f[i-1] + a[i] (此处,原数组为 a,前缀和数组为 f 。f[i] 表示:a 数组 [1,i] 区间所有元素的和)
2. 查询区间 [ l,r ] 的和:f[r]-f[l-1]
2.2 最大子段和
2.2.1 题目描述
给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大(1<=n<=2e5)
输入描述:
第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai (-1e4<=ai<=1e4)
输出描述:
一行一个整数表示答案
输入:
7
2 -4 3 -1 2 -4 3
输出:
4
2.2.2 算法分析
因为要选出连续的一段,我们可以考虑以 i 位置元素 a[i] 结尾的最大子段和就是结果。而这个子段和相当于我们用前缀和 f[i] 减去 i 位置前的某个位置 f[x],要想让这个字段和最大,那么 f[i] 要减去最小的 【前驱子段】。
所以,我们可以创建 a 数组的前缀和数组 f ,在遍历 f 数组的过程中,更新 【当前位置为结尾的最大子段和】同时更新 【前驱子段最小值】
2.2.3 代码实现
#include<iostream>
using namespace std;
const int N = 2e5 + 10;
typedef long long LL;
LL 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;}//标记最大子段和,初始为负无穷(如果为0可能会干扰结果)LL ret = -1e9;//【最小前驱子段和】初始为0,因为如果前缀和下标为1,不存在前驱子段LL 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;
}
3. 二维前缀和
3.1 模板
1. 套用公式创建前缀和矩阵:f[i][j] = f[i-1][j] + f[i][j-1] - f[i-1][j-1] + a[i][j] (此处,原数组为 a,前缀和矩阵为 f 。f[i][j] 表示:a 矩阵左上角 [1,1] ,到右下角 [i][j] 所有元素的和)
2. 查询 (x1,y1) 为左上角,(x2,y2)为右下角的子矩阵的和
3.2 激光炸弹
3.2.1 题目描述
一种炸弹摧毁一个边长为 m 的正方形内的所有目标。现在地图上有 n 个目标,用整数 xi , yi 表示目标在地图上的位置,每个目标都有一个价值 vi。激光炸弹有一个缺点,就是其爆破范围,即那个边长为 m 的边必须与 x 轴,y 轴平行。若目标位于爆破正方形的边上,该目标不会被摧毁。
现在你的任务是计算一颗炸弹最多能炸掉地图上总价值为多少的目标。
可能存在多个目标在同一位置上的情况。
输入描述:
输入的第一行为整数 n 和整数 m;(1<=n<=1e4,1<=m<=5e3)
接下来的 n 行,每行有 3 个整数 x,y,v,表示一个目标的坐标与价值。
输出描述:
输出仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过 32767 )。
输入:
2 1
0 0 1
1 1 1
输出:
1
3.2.2 算法分析
根据题目,我们可以先使用一个二维矩阵将每个位置的价值叠加起来,即 a[i][j] 表示位置 [i,j] 的所有目标价值之和。因为激光炸弹获得的价值正好是 R*R 的矩阵所有目标价值的和,所以可以使用二维前缀和快速求出矩阵的和,枚举所有变长为 R 的矩阵的和(只需枚举右下角(x2,y2),左上角为(x2-R+1,y2-R+1)),选出最大值即可
3.2.3 代码实现
#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;while (n--){int x, y, v; cin >> x >> y >> v;//下标从 1 开始x++, y++;//同一个位置所有目标价值叠加a[x][y] += v;}//矩阵的范围n = 5001;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] + a[i][j] - f[i - 1][j - 1];}}int ret = 0;//如果边长大于区域,相当于摧毁整个区域m = min(m, n);//枚举所有变长为 m 的矩阵//注意右下角从(m,m) 开始for (int x2 = m; x2 <= n; x2++){for (int y2 = m; y2 <= n; y2++){//求左上角int x1 = x2 - m + 1, y1 = y2 - m + 1;//得到最大值ret = max(ret, f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 - 1][y1 - 1]);}}cout << ret << endl;return 0;
}