目录
1.前缀和
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。
是经典的用空间替换时间的做法。
1.1一维前缀和
公式推导过程
1.先创建前缀和数组
f[i] = f[i-1] + a[i]
时间复杂度为o(n);
2.查询区间和[l,r] : f[l] - f[r-1]
时间复杂度为o(q);(q次查询)
注意事项:使用前缀和数组时,下标最好从1开始,可以预防很多越界的情况。
1.1.1模板题:
链接: link
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N];
LL f[N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1;i <= n;i++) cin >> a[i];
for(int i = 1;i <= n;i++)
{
f[i] = f[i-1] + a[i];
}
while(m--)
{
int l, r;
cin >> l >> r;
cout << f[r] - f[l-1] << endl;
}
return 0;
}
1.1.2练习题
链接: link
P1115 最大子段和
题目描述
给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度 n n n。
第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入
7
2 -4 3 -1 2 -4 3
输出
4
说明/提示
样例 1 解释
选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,−1,2},其和为 4 4 4。
数据规模与约定
- 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 10 3 n \leq 2 \times 10^3 n≤2×103。
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 10 5 1 \leq n \leq 2 \times 10^5 1≤n≤2×105, − 10 4 ≤ a i ≤ 10 4 -10^4 \leq a_i \leq 10^4 −104≤ai≤104。
#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;
}
LL ret = -1e20;
LL premin = 0;//前驱最小值
for (int i = 1; i <= n; i++)
{
ret = max(f[i] - premin, ret);//最大子段和 - 前驱最小值
premin = min(premin, f[i]);
}
cout << ret << endl;
return 0;
}
2.1二维前缀和
二维前缀和:直接套拥「公式」创建前缀和矩阵,然后利拥前缀和矩阵的「性质」处理 q 次询问。
1.创建前缀和矩阵: f[i][j] = f[i − 1][j] + f[i][j − 1] − f[i − 1][j − 1] + a[i][j]
2. 查询以 (x1 , y1 ) 为左上角 , (x2 , y2 ) 为右下角的子矩阵的和
2.1.1模板题
链接: link
#include <iostream>
using namespace std;
int n, m, q;
const int N = 1e3 + 10;
typedef long long LL;
LL f[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--)
{
int x1, y1,x2,y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << f[x2][y2] - f[x2][y1-1] - f[x1-1][y2] + f[x1-1][y1-1] << endl;
}
return 0;
}
2.1.2 练习题
链接: link
P2280 [HNOI2003] 激光炸弹
题目描述
一种新型的激光炸弹,可以摧毁一个边长为 m m m 的正方形内的所有目标。现在地图上有 n n n 个目标,用整数 x i x_i xi , y i y_i yi 表示目标在地图上的位置,每个目标都有一个价值 v i v_i vi。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为 m m m 的边必须与 x x x 轴, y y y 轴平行。若目标位于爆破正方形的边上,该目标不会被摧毁。
现在你的任务是计算一颗炸弹最多能炸掉地图上总价值为多少的目标。
可能存在多个目标在同一位置上的情况。
输入格式
输入的第一行为整数 n n n 和整数 m m m;
接下来的 n n n 行,每行有 3 3 3 个整数 x , y , v x, y, v x,y,v,表示一个目标的坐标与价值。
输出格式
输出仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过 32767 32767 32767 )。
输入输出样例
输入
2 1
0 0 1
1 1 1
输出
1
说明/提示
数据规模与约定
对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 10 4 1 \le n \le 10^4 1≤n≤104, 0 ≤ x i , y i ≤ 5 × 10 3 0 \le x_i ,y_i \le 5\times 10^3 0≤xi,yi≤5×103, 1 ≤ m ≤ 5 × 10 3 1 \le m \le 5\times 10^3 1≤m≤5×103, 1 ≤ v i < 100 1 \le v_i < 100 1≤vi<100。
#include <iostream>
using namespace std;
int n, m;
const int N = 1e4 + 10;
int f[N][N];
int a[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
int x, y, v;
cin >> x >> y >> v;
x++;y++;//前缀和下标是从1开始的,所以这里要加1;
a[x][y] += v;//同一个位置有可能有多个目标
}
n = 5010;
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);//如果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;
}