学了忘忘了学o(╥﹏╥)o
题源acwing
- 讲解前缀和
- 一维,用于序列
- 二维,用于矩阵
- 讲解差分
- 什么是差分数组?
- 一维差分数组
- 二维差分数组
- 题目一:前缀和
- 题目二:子矩阵的和
- 题目三:差分
- 题目四:差分矩阵
讲解前缀和
什么时候用到前缀和呢?一般题目涉及到数组任意序列或者矩阵的话都有可能考点是前缀和。
一维,用于序列
具体做法:
首先做个预处理,定义一个sum[]数组,sum[i]代表a数组中前i个数的和。
求前缀和运算:
const int N = 1e5+10;
int sum[N], a[N]; //sum[i] = a[1] + a[2] + a[3] ..... a[i];
for(int i = 1; i <= n;i++)
{
sum[i] = sum[i - 1] + a[i];
}
如果想知道a1…an中任意一段的和,比如 a[l] 到a[r]
scanf("%d%d", &l, &r);//l是起始,r是终点
printf("%d\n", sum[r] - sum[l - 1]);//因为需要包括a[l]
二维,用于矩阵
矩阵的s[i, j]是表示以(1,1)为左上角节点,(i,j)为右下角节点的的矩阵和
求二维前缀和公式为:
s[i,j] = s[i - 1, j] + s[i, j - 1] - s[i - 1][j - 1] + a[i][j]
所以应用在代码上:
// 求 s[i, j]
for(int i = 1; i <= n; i++){
for(int j = 1; j <=m; j++){
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
如果想知道以(x1, y1)为左上角坐标,(x2, y2)为右下角坐标的句子
cout << s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]
讲解差分
什么是差分数组?
差分一听,啥jb玩意啊,其实就是前缀和倒过来,s是a的前缀和数组,a是s的差分数组
未来的小dream
我本来打算这里简单写的,但是感觉我未来会有忘记的一天,痛定思痛写详细一点o(╥﹏╥)o谁赐我一个过目不忘的脑子
这块注意是,原数组a改动了之后,对应前缀和数组s的变化
假设a数组[3,1,2,5,7],对应s数组是[3,4,6,11,18]
将a[2]加3之后,对应s数组变成[3,4,6+3,11+3,18+3]
再将a[3]减3,对应s数组编程[3,4,6+3,11+3-3,18+3-3]
如下图
ok,未来的小dream,知道啥意思了吗
总结两点:
原数组单点操作,对应前缀和数组的区间操作
前缀和数组的区间操作,对应原数组的单点操作
这两点之间来回切换思考题目反应要快
次背景下可以有一种题型,题目对a进行多次区间操作,其变化后的a数组!
ok,怎么思考?
对s进行区间操作,之后求a数组,是不是更好求一点,但是题目是对a进行区间操作,怎么办?
可以构造出一个b数组,b数组的前缀和是a数组,现在对a数组进行区间操作,就是对b数组的前缀和数组进行操作,最后将变化后的前缀和数组对应到b上,在从b还原出数组a
简单的说:将对a的区间操作,转换为对b的两点操作,最后由b还原a
这里的b数组就是a的差分数组,
一维差分数组
忘了的再回忆一遍,a是b的前缀和,b是a的差分,a[i][j] = b[1][1] +…+b[i][j]
ok,继续
操作一:给区间[l, r]中的每个数加上c
b[l] += c,b[r + 1] -= c
操作二:知道a数组求b数组
b[i] = a[i] - a[i - 1]
二维差分数组
扩展到二维。s[][]是a[][]的前缀和数组,那么a[][]是s[][]的差分数组
假设已经构造好了a[][]。
操作:构建差分数组
相当于在全为0的二维数组加,所以加值都是一样的
现在来执行加的操作
a[x1][y1] += c;
a[x1][y2+1] -= c;
a[x2+1][y1] -= c;
a[x2+1][y2+1] += c
等价于:
for(int i = x1; i <= x2; i ++)
for(int j = y1; j <= y2; j ++)
s[i][j] += c;
其实也很好理解
将加法操作封装成一个函数
void insert(int x1,int y1,int x2,int y2,int c)
{ //对a数组执行插入操作
//等价于对s数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
a[x1][y1]+=c;
a[x2+1][y1]-=c;
a[x1][y2+1]-=c;
a[x2+1][y2+1]+=c;
}
题目一:前缀和
蛮简单属于前缀和入入入入入入门级题目,只涉及到了序列
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];//保存原数组
int s[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 ++){
s[i] = s[i - 1] + a[i];
}
while(m --){
int l, r;
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
}
其中需要注意的地方:
a数组也就是原数组,因为s[i]数组的含义是前i个字符和,为了和s[i]对应,a[0]应该初始化为0,当然s[0]也是0
题目二:子矩阵的和
也是前缀和矩阵的入门级题目了,我这次好好总结,以后再也不忘记了
┭┮﹏┭┮
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int a[N][N];
int s[N][N];
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];
}
}
//计算前缀和s[i][j]
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; 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[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] << endl;
}
}
和第一题一样要注意,下标都从1开始
题目三:差分
首先构造差分数组,接着对应在差分数组上进行两点操作
最后将对应差分数组转化为序列输出
小dream,o(╥﹏╥)o劝你好好学习
很多题解会用a,b数组
我为了和前缀和形成对应还是用s和a数组
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int s[N];
int main(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin >> s[i];
a[i] = s[i] - s[i - 1];
}
while(m --){
int l, r, c;
cin >> l >> r >> c;
a[l] += c;
a[r + 1] -= c;
}
for(int i = 1; i <= n; i ++){
s[i] = s[i - 1] + a[i];
cout << s[i] << " ";
}
}
没有要注意的,小dream多细点心,然后segment fault有可能是因为你数组开的不够大
题目四:差分矩阵
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int s[N][N];
int a[N][N];
void insert(int x1, int y1, int x2, int y2, int c){
a[x1][y1] += c;
a[x1][y2 + 1] -= c;
a[x2 + 1][y1] -= c;
a[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 >> s[i][j];
}
}
//构建差分数组
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
insert(i, j, i, j, s[i][j]);
}
}
//对a进行操作
while(q --){
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
//由a推出s,即可得到答案
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
cout << s[i][j] << " ";
}
cout << endl;
}
}