前缀和
前缀和本质上是等差数列
前缀和是一种常见的算法思想,适用于处理与数组区间求和相关的问题,能够提高查询效率
那么什么时候会想到前缀和呢?
1)高频的区间与查询:当你需要频繁查询数组或矩阵的某些区间的总和时,前缀和可以极大的优化效率
以下为几个场景:1,频繁区间和查询,和为特定值的子数组,二维区域求和,统计特定性质的子数组
以下为前缀和795 练习题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int s[N],a[N];
int main()
{
int m,n;
cin >> n >> m;
for(int i = 1;i<=n;i++)
{
cin >> a[i];
}
for(int i = 1;i<=n;i++)
{
s[i] = s[i-1] + a[i];
}
while(m--)
{
int l,r;
cin >> l >> r;
cout << s[r] - s[l-1] << endl;
}
return 0;
}
这就是应用的前缀和,首先输入值,然后对每一个位置的区间和为等差数列的公式,最后在主函数里面分别输入左边界和右边界,然后从第一个位置到最右边减去第一个位置到做左边的左边的数。
子矩阵的和
这里本质上也是运用的前缀和,但是有一定的技巧‘
已混点:就是坐标,明确坐标时在区间内
#include<iostream>
using namespace std;
const int N = 1010;
int s[N][N],a[N][N];
int main()
{
int m,n,q;
cin >> n >> m >> q;
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
cin >> a[i][j];
//前缀和
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
}
while(q--)
{
int x1,y1,x2,y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] << endl;
}
return 0;
}
这个一开始不好理解,多理解就好啦
差分
什么是差分呢?首先理解
以下为介绍:
差分算法 是一种高效处理 区间修改 问题的算法技巧,主要适用于一维或者二维数据,主要是勇敢记录变化量来简化区间操作
核心思想:差分数组记录相邻元素的差值,通过差分数组进行操作,可以快速完成区间内的加减更新
那么好处是什么呢?
高效处理多次区间操作: 对区间的直接修改需要遍历整个区间效率较低
差分算法只需要记录变化,查询复杂度会为O(1)
内存消耗小,差分数组大小与原数组相同,无需额外存储每次操作的结果
#include<iostream>
using namespace std;
cosnt int N = 100010;
int a[N];
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1;i<=n;i++)
{
cin >> a[i];
b[i] = a[i] - a[i-1];
}
int l,r,c;
while(m--)
{
cin >> l >> r >> c;
b[l] += c;
b[r + 1] -= c;
}
for(int i = 1;i<=n;i++)
{
a[i] = b[i] + a[i-1];
cout << a[i] << ' ';
}
return 0;
}
这里画一个图
你就理解了
大体上就是这个思路
但是如果按照上面的思路的话可能会显示段错误,反正我是这样的
那么应该怎么改进呢?
段错误: 最常见的原因是数组下标越界或者使用了未初始化的内存
比如说如果读到人r = n 这里会导致越界,所以要特殊说明
#include <bits/stdc++.h>
using namespace std;
// 如果 n 上限是 10^5,通常开到 10^5+5 或更大,避免下标 r+1 越界
static const int MAXN = 100000 + 5;
int a[MAXN], b[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
// 读入原始数组
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
// 为了安全,显式地设置 a[0] = 0,
// 如果 a、b 是全局 static/全局数组,在 C++ 中会默认初始化为 0,但加上更稳。
a[0] = 0;
// 构建差分数组:
// b[i] = a[i] - a[i-1]
for (int i = 1; i <= n; i++) {
b[i] = a[i] - a[i - 1];
}
// 读入 m 次操作,每次把区间 [l, r] 的元素都加 c
while (m--) {
int l, r, c;
cin >> l >> r >> c;
// 差分区间加法
b[l] += c;
// 如果 r < n,再对 b[r+1] -= c,避免访问越界
if (r < n) {
b[r + 1] -= c;
}
}
// 通过差分数组 b 重新计算出 a
// a[i] = a[i-1] + b[i]
// 注意这里若要从头开始计算,需要把 a[0] 设成 0
for (int i = 1; i <= n; i++) {
a[i] = a[i - 1] + b[i];
}
// 输出结果
for (int i = 1; i <= n; i++) {
cout << a[i] << (i == n ? '\\n' : ' ');
}
return 0;
}
差分矩阵
这个我认为是难度最大的
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。
每个操作都要将选中的子矩阵中的每个元素的值加上 c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
这个是题目
下面我画一个图,类似于子矩阵的和
相信这样大家就理解啦
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e3 + 10;
int a[N][N],b[N][N];
void insert(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
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];
}
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
insert(i,j,i,j,a[i][j]);
}
}
while(q--)
{
int x1,y1,x2,y2,c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1,y1,x2,y2,c);
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
}
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
printf("%d ",b[i][j]);
}
printf("\\n");
}
return 0;
}
ok了,这个是1月12日的差分与前缀和整理