【算法笔记】-前缀和和差分
【算法笔记】-前缀和和差分
问题导入:
给定n个数据,他们的序号从1到n。现在有m次询问,每次给到两个数字x,y,需要输出区间[x,y]的和。
如果我们暴力解决这个问题,时间复杂度为O(n*m)。如果数据规模达到10^5以上,就会超时。
优化思路:对数据预处理,使得能在O(1)时间内实现查询,显然区间[x,y]的和等于[1,y]的和减去[1,x-1]的和。这里我们需要预处理出全部1到i(1<=i<=n)的和。这种处理我们称之为前缀和。
所谓前缀和可以理解为数学里的数列前n项和,是用来求子区间和的一种便捷方法,与暴力解法相比,其时间复杂度低,大大提高了运行效率。
对于以上问题,可以写出如下代码解决:
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],sum[N];
int main(){
int n,m,l,r;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
sum[i]=sum[i-1]+a[i];//预处理前缀和
}
for(int i=1;i<=m;i++)
{
cin>>l>>r;
cout<<sum[r]-sum[l-1]<<endl;
}
return 0;
}
以上是一维前缀和,如果是二维的呢?
和一维前缀和一样,我们需要构造一个前缀和数组sum[i][j]。利用前缀和的思想:我们需要通过前面已知的前缀和来推出后面未知的前缀和。
红色大框的总和为sum[i][j]。实际上所有临近的线条都应该重叠的,为了方便观察错开了一点。根据幼儿园的知识,显然红色矩形的面积=绿框面积+蓝框面积-黑框面积。
转换成前缀和数组:sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
查询一个区间的数据和,
假设左上角是(a,b),右下角是(c,d) 那么包含这两个点的矩形中的数据和为:
ans=sum[c][d]-sum[c][b-1]-sum[a-1][d]+sum[a-1][b-1];
#include <iostream>
using namespace std;
int a[1005][1005];
int main()
{
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
//预处理前缀和数组
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
s[i][j]=s[i-1][j]+s[i][j-1]+a[i][j]-s[i-1][j-1];
}
//查询
while(m--){
int x,y,c,d;cin>>x>>y>>c>>d;
cout<<s[c][d]-s[x-1][d]-s[c][y-1]+s[x-1][y-1]<<endl;
}
return 0;
}
差分
所谓差分可以理解为前缀和的逆运算,就是将数列中每一项分别与前一项做差。
经过简单计算验证,对差分数组求前缀和可以得到原数组,对前缀和数组求差分可以得到原数组。
差分一般用来解决区间同时加一个数的问题。
【题目描述】
输入一个长度为n的整数序列。接下来输入m个操作,每个操作包含三个整数l,r, c,现将序列中[l, r]之间的每个数加上c。请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数n和m。第二行包含n个整数,表示整数序列。接下来m行,每行包含三个整数l, r, c,表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。
数据范围
1<=n,m<=100000
1<=l<=r<=n
-1000<=c<=1000
-1000<=整数序列中元素的值<=1000
输入样例
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例
3 4 5 3 4 2
根据差分和前缀和的性质,对于差分数组b的下标为l加上c的操作等于对于原数组a的下标为l之后的所有元素加上c的操作,由此可见,若是在一维层面上对于区间元素操作则可以解释为
对于原数组在区间[L,R]每个数加上c相当于在差分数组L位置加上C,在R+1位置减去c。
对于所给的一维数组a[n],我们先要构造其一维差分数组d[n]。按照差分的定义我们很容易就能想到:d[n]=a[n]-a[n-1]
给[L,R]区间加上c:d[L]+=c;d[R+1]-=c;
完整代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],d[N];
int main(){
int n,m,l,r,c;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
d[i]=a[i]-a[i-1];//预处理差分数组
}
for(int i=1;i<=m;i++)
{
cin>>l>>r>>c;
d[l]+=c;d[r+1]-=c;
}
for(int i=1;i<=n;i++)
{
d[i]=d[i-1]+d[i];//求前缀和恢复原数组
cout<<d[i]<<' ';
}
return 0;
}
二维差分问题:
根据前面和二维前缀和和差分的知识,显然:
题目描述
输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, 2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。
输入格式
第一行包含整数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>
using namespace std;
const int N = 1000+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[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= 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];
//预处理差分矩阵
b[i][j]=a[i][j]-a[i][j-1]-a[i-1][j]+a[i-1][j-1];
}
}
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];
cout<<b[i][j]<<' ';
}
cout<<endl;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】