前缀和和差分
这是基础课算法第一节,主要是讲了,前缀(二维)和 和差分。
那好我们来想想什么是前缀和呢?如果我们去oi wiki和买过一些黑皮书,它们告诉我们的答案都是前缀和类似于高中所学的数列前n项和,高中数列有两个公式很重要,一个是等差数列公式,一个是等比数列公式,当然这与我们所讲的内容关系不大,只是一个联想。
相对的前缀和也有两个公式,一个相加求和,一个相减求和
可以看到通过相加来推出相减,而且只需要一次计算,就能得到结果。
前缀和数组的定义
假设有一个数组 arr
,它的前缀和数组 prefix
定义为:
prefix[0]
=arr[0]
prefix[1]
=arr[0] + arr[1]
prefix[2]
=arr[0] + arr[1] + arr[2]
- 以此类推
一般地,prefix[i]
表示原数组 arr
从索引 0 到索引 i
的所有元素的和
我们看一道题
有 N 个的正整数放到数组 A里,现在要求一个新的数组 B,新数组的第 i 个数 是原数组 A第i0到第
i个数的和。
输入:
5
1 2 3 4 5
输出:
1 3 6 10 15
思路:原数组B[0]与A[0]相同,B[i]=B[i-1]+A[i]。
代码如下:
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int N,A[1000],B[1000];
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>N;
for(int i=0;i<N;i++)cin>>A[i];//先输入A数组的值
B[0]=A[0];//先表示第1项相同
for(int i=1;i<N;i++)
{
//前缀和数组第i项=原数组的0到i-1项的和+原数组的第i项
B[i]=B[i-1]+A[i];
}
for(int i=0;i<N;i++)
cout<<B[i]<<" ";
return 0;
}
题目不难,思路比较简单,适合新手。
下面是前缀和的模版,如果实在想不到时可以看看。
题目描述
给定义一个数组a,有q+1次询问,对于每次询问:
给定两个整数l,r求al+al+1+...+ar的结果。
输入描述
第一行一个整数表示样例个数T(1≤T≤10)T(1≤T≤10)。
对于每组样例:
第一行2个整数n(1≤n≤105),q(1≤q≤105),分别表示数组长度和询问次数。
第二行n个整数,表示数组a(−109≤ai≤109)。
接下来q行,每行两个整数l,r(1≤l≤r≤n)询问的区间。
输出描述
对于每组样例,一行一个整数表示答案。
#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
using ll=long long;
const int N=2e5+9;
ll a[N],prefix[N];//prefix[N]表示一个前缀和数组第n个元素
void solve()
{
int n,q;cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)prefix[i]=prefix[i-1]+a[i];//相加
while(q--)
{
int l,r;cin>>l>>r;
cout<<prefix[r]-prefix[l-1]<<'\n';//相减
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _;cin>>_;
while(_--)solve();
return 0;
}
以上是一维前缀和数组,现在我们来讲,二维前缀和数组。
二维前缀和的模版图(容斥原理)
好,或许大家还有疑问,这个模版跟二维前缀和数组有什么关系?相信看到这里的算法内容的同学应该知道二维数组是什么,相对应的二维前缀和数组,就是在二维数组上面进行求和,那怎么求和呢?
模版上,我们看到的是如果要求prefix[3][3],那就是先加上i-1和j-1的区间,然后再减去i-1和j-1的区间,这是为什么呢?因为这个区间我们已经算了两次了,所以要再减一次,这样才没有重复计算,最后再加上a[i][j]就行了。
如果题目中有坐标轴的问题,下列是分析模版(请一定记住)
好,既然你已经知道了二维前缀和数组,那就来做道题吧!
给定一个n行m列的整数矩阵。
有q个询问,每个询问格式为:x1,y1,x2,y2,表示一个子矩阵的左上角和右下角的坐标。
对于每个询问,请回答子矩阵的所有数之和。
输入格式
第一行包括三个整数n,m,q(1≤n,m≤103,1≤q≤105)。
接下来n行,每行包括mm个整数,表示整数矩阵(每个整数的取值范围为[1,105])。
接下来q行,每行包括四个整数x1,y1,x2,y2x1,y1,x2,y2(1≤x1≤x2≤n1≤x1≤x2≤n,1≤y1≤y2≤m1≤y1≤y2≤m),表示一个询问的左上角、右下角坐标。
输出格式
共q行,第i(1≤i≤q)行输出第i个询问的结果。
样例输入1:
7 3 2
3 5 1
6 2 4
7 9 10
4 3 6
3 9 9
6 10 1
9 10 4
2 2 7 3
2 1 4 2
样例输出1
77
31
这题看似简单,实则一点也不难
上代码(建议不要学我,我是懒得打,记得自己先想,想不出来再看):
#include <bits/stdc++.h>
#include <assert.h>
using namespace std;
using ll=long long;
const int N=1e3+9;
ll a[N][N],p[N][N];
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
ll 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++)
p[i][j]=p[i][j-1]+p[i-1][j]-p[i-1][j-1]+a[i][j];
while(q--)
{
int x_1,y_1,x_2,y_2;cin>>x_1>>y_1>>x_2>>y_2;
cout<<p[x_2][y_2]-p[x_1-1][y_2]-p[x_2][y_1-1]+p[x_1-1][y_1-1]<<'\n';
}
return 0;
}
说实话我也在想为什么x1,x2,y1,y2要用下标才能表示,CP上只有这样才能运行。
差分数组的思想和模版图
先上差分模版,看几分钟。
看到这个图片左边这个部分能想到什么呢?是不是很像裂项相消?
通过前n项和相加,我们能看到j开始到i这个区间相加最后得到的结果就是ai,上面的di就是diff(差分数组的表达方式)di=ai-ai-1,比如数组arr[1,2,3],那么diff[1,1,1];这个好理解,右边这个部分代表的是diff区间的加减,简单来说就是在一段差分数组的子区域内进行加减,然后再进行前缀和得到更新后的数组。
那差分数组的定义是什么呢?
差分数组的定义
给定一个数组 arr
和一个差分数组 diff
,我们可以通过以下方式定义差分数组:
diff[i]
表示arr[i]
与arr[i-1]
之间的差值,即diff[i] = arr[i] - arr[i-1]
(对于i = 0
,我们通常定义diff[0] = arr[0]
)
步骤如下:
-
初始化差分数组:首先,需要从原数组
arr
生成差分数组diff
。 -
区间更新:要在
arr
的某个区间[l, r]
上进行加x
的操作,可以通过差分数组diff
来实现:- 增加
x
到diff[l]
- 减去
x
从diff[r + 1]
(如果r + 1
在数组范围内)
- 增加
3.恢复原数组:在所有更新完成后,可以通过差分数组恢复原数组 arr
:
- 对
diff
数组进行前缀和操作,得到更新后的arr
好了,你已经了解差分数组了,来做个题
给定一个长度为n的数组a,和两个整数p,q。
先进行p次区间加操作:将区间[l,r]的数字都加上x。
再进行q次区间查询操作:求出[l,r]的数字之和。
对于每次区间查询操作,输出结果。
输入描述
第一行三个整数n,p,q。(1≤n≤105,0≤p≤105,0≤105≤q)
第二行n个整数表示数组a。(−109≤ai≤109)
接下来p行,每行三个整数l,r,x。(1≤l≤r≤n,−109≤x≤109)
接下来q行,每行两个整数l,r。(1≤l≤r≤n)
输出描述
对于每次区间查询操作,输出结果。
输入样例1
5 1 2
1 1 1 2 2
1 4 2
1 3
1 5
输出样例1
9
15
跟上面一题一样,先别看想一下再做
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e3+9;
ll a[N],diff[N],prefix[N];
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
ll n,p,q;
cin>>n>>p>>q;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)diff[i]=a[i]-a[i-1];
while(p--)
{
ll l,r,x;cin>>l>>r>>x;
diff[l]+=x;diff[r+1]-=x;
}
for(int i=1;i<=n;i++)a[i]=a[i-1]+diff[i];//更新后的原数组=原数组i-1+diff[i]
for(int i=1;i<=n;i++)prefix[i]=prefix[i-1]+a[i];//用前缀和进行累加起来
while(q--)
{
ll l,r;
cin>>l>>r;
cout<<prefix[r]-prefix[l-1]<<'\n';
}
return 0;
}
OK, 我们还剩最后一个内容请继续坚持
先送一段话:“there must be a beginning of every great matter,but the continuing unto the end until it be thoroughly finished yields the true glory”
然后开始我们二维差分数组的学习!
先上二位差分模版图片!是不是和二维前缀和数组很相似?
我们看到图片二维差分是为了在原矩阵上绿色的方框内+X,那做法呢?
与二维前缀和数组相似,还是先减后加,把重复减的部分记得加一次,就能得到我们想要的区间了
那二维差分数组的定义是什么呢 ?
二维差分数组的定义
1.二维差分数组的初始化
-
给定一个
m x n
的矩阵matrix
,我们定义一个m x n
的差分数组diff
。(二维差分数组用于高效地处理矩阵中区域的加法操作) -
差分数组的更新
diff[row1][col1] += x
diff[row1][col2 + 1] -= x
(如果col2 + 1
在范围内)diff[row2 + 1][col1] -= x
(如果row2 + 1
在范围内)diff[row2 + 1][col2 + 1] += x
(如果row2 + 1
和col2 + 1
在范围内)- 对于要在矩阵中的某个子矩形区域
[row1, col1]
到[row2, col2]
进行加x
的操作,可以通过二维差分数组diff
实现
3恢复原矩阵更新完成后,需要恢复原矩阵 matrix
。具体步骤如下
- 创建一个
m x n
的矩阵matrix
,用来存储最终的结果 - 使用二维前缀和算法来计算每个位置的实际值。
好,我们来做最后一题
题目描述
给定一个n行m列的整数矩阵。
有q个操作,每个操作格式为:x1,y1,x2,y2,c ,其中(x1,y1)(x1,y1)、(x2,y2)(x2,y2)分别表示一个子矩阵的左上角和右下角的坐标,每个操作将对应的子矩阵的每个元素加上c。
请输出进行完所有操作后的矩阵。
输入描述
第一行包括三个整数n,m,q(1≤n,m≤103,1≤q≤105)。
输出描述
共n行,每行包括m个整数,表示进行完所有操作后的矩阵。
输入样例1
4 3 3
1 5 1
3 3 2
5 3 4
4 4 2
1 2 1 2 2
2 1 2 3 2
4 2 4 3 1
输出样例1
1 7 1
5 5 4
5 3 4
4 5 3
最后一题了,听我一句劝,还是不要再抄了,不然对自己提升的效果不大的
#include<bits/stdc++.h>
using namespace std;
using ll= long long;
const ll N=1e3+9;
ll a[N][N],p[N][N],d[N][N];
int main()
{
ios::sync_with_stdio(0),cout.tie(0),cin.tie(0);
ll 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)
{
d[i][j]+=a[i][j];
d[i+1][j]-=a[i][j];
d[i][j+1]-=a[i][j];
d[i+1][j+1]+=a[i][j];
}
//进行修改操作
while(q--)
{
int x1,y1,x2,y2,c;cin>>x1>>y1>>x2>>y2>>c;
d[x1][y1]+=c;
d[x1][y2+1]-=c;
d[x2+1][y1]-=c;
d[x2+1][y2+1]+=c;
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+d[i][j];
cout<<a[i][j]<<' ';
}
cout<<'\n';
}
return 0;
}
好了,总结:差分数组(diff)和前缀(prefix)和都是在为原数组a[i]进行修改把它当成一个高中数列求前n项和的问题
这是第一次在csdn上发博客,如有问题还请见谅。