今天来介绍一下前缀和区间和。
前缀和
前缀和分为一维前缀和和二维前缀和。
在下文中,我们指定输入的数组是a,前缀和数组是s,坐标是x和y。
一维前缀和
公式
s[i]=s[i-1]+a[i]。
作用
通过它的名字可知,这可以求一个一维数组里任意数的前缀和。并且因为它是用计算的方式求出前缀和,所以可以降低时间复杂度。
原理
一个数(a[i])的前缀和(s[i])是怎么来的呢?就是把前面全部加起来,并且再加上它自己。想必你发现了,前面全部加起来的和可不就是它前一位(a[i-1])的前缀和(s[i-1])嘛。既然知道了s[i-1]代表了它前面的总合,就可以把一维前缀和公式写出来了。
代码例子
#include <bits/stdc++.h>
using namespace std;
int a[10],s[10]; //定义
int main() {
for (int i=1;i<=5;i++) {//循环输入
cin>>a[i]; //输入:1 2 3 4 5
s[i]=s[i-1]+a[i]; //求每个数的一维前缀和
}
for (int i=1;i<=5;i++) {//循环输出
cout<<s[i]<<" "; //输出:1 3 6 10 15
}
return 0;
}
二维前缀和
公式
s[x][y]=s[x-1][y]+s[x][y-1]-s[x-1][y-1]+a[x][y]。
作用
与一维前缀和一样,都减少了时间复杂度。不过它是求一个二维数组中任意数的前缀和,所以公式中的x和y可替换为循环的工具变量i和j。
原理
它相较于一维前缀和的原理更复杂,可以自己试着画一下。
要求以(1,1)作为左上角坐标,(x,y)作为右下角坐标的红色框(s[x][y])内的所有值总和
绿色框右下角的坐标是(x,y-1),蓝色框右下角的坐标是(x-1,y),紫色框右下角的坐标是(x-1,y-1),又黄又绿的框的右下角坐标是(x,y),虽然和红色框的右下角坐标一样,但代指的范围不一样,它指的是a[x][y]。

我们将绿色框看作区间1,蓝色框看作区间2,紫色框看作区间3,又黄又绿的框看作区间4,要求的是红色框。红色框应该怎么求?不难发现,区间1(绿色框)加上区间2(蓝色框)再减去区间3(紫色框)可以得出区间4以外的值,那么再加上区间4就可得出红色框的值。已知我们要求二维前缀和的点的坐标是(x,y),所以区间1的值可以用s[x][y-1]表示(同行不同列),区间2的值可以用s[x-1][y]表示(同列不同行),区间3的值可以用s[x-1][y-1]表示(不同行也不同列),区间1的值可以用a[x][y]表示(这一个点的值)。然后就可得到此公式了。
代码例子
#include <bits/stdc++.h>
using namespace std;
int a[10][10],s[10][10]; //定义
int main() {
for (int i=1;i<=5;i++) { //循环输入(行)
for (int j=1;j<=5;j++) { //循环输入(列)
cin>>a[i][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<=5;i++) { //循环输出(行)
for (int j=1;j<=5;j++) { //循环输出(列)
cout<<s[i][j]<<" "; //输出在下面
}
cout<<endl;
}
return 0;
}
//输入: 1 2 3 4 5 输出: 1 3 6 10 15
// 1 1 1 1 1 2 5 9 14 20
// 0 1 0 0 1 2 6 10 15 22
// 1 1 1 1 0 3 8 13 19 26
// 2 0 1 1 4 5 10 16 23 34
区间和
区间和分为一维区间和和二维区间和。
变量e和b代表的是要求的区间的结尾和开头,坐标是x和y。
一维区间和
公式
s[e]-s[b-1]。
原理
一个数的一维区间和是怎么求的呢?如果列一串数字不难发现,这个区间的区间和就是它结尾的那个数(a[e])的前缀和(s[e])减去他开头的那个数(a[b])的前一个数(a[b-1])的前缀和(s[b-1])。因为如果将从开头到区间结尾和从开头到区间开头前一个这两个分别看作一个整体,就会发现它们相减的差就是要求的区间和(建议自己列一下数字)。
代码例子
#include <bits/stdc++.h>
using namespace std;
int a[10],s[10]; //定义
int main() {
for (int i=1;i<=5;i++) {//循环输入
cin>>a[i]; //输入:1 2 3 4 5
s[i]=s[i-1]+a[i]; //求每个数的一维前缀和
} //s数组的值1 3 6 10 15
cout<<s[4]-s[2]<<" "; //输出3到4的区间和(7)
return 0;
}
二维区间和
公式
s[x][y]-(s[x][j-1]+s[i-1][y]-s[i-1][j-1])。(公式中的i和j是循环的工具变量i和j。)
原理
与二维前缀和不同的是,二维区间和求的数可以不只一个。
红色框右下角的坐标是(x,y),绿色框右下角的坐标是(x,i-1),蓝色框右下角的坐标是(i-1,y),紫色框右下角的坐标是(i-1,j-1),黄色框的右下角坐标是(x,y),虽然同样和红色框的右下角坐标一样,但代指的范围也不一样。

我们将红色框看作区间1,绿色框看作区间2,蓝色框看作区间3,紫色框看作区间4,要求的是黄色框。黄色框应该怎么求?不难发现,区间2(绿色框)加上区间3(蓝色框)减去区间4(紫色框)可以得出黄色框以外的值,那么再让区间1(红色框)减去它们就可得出黄色框内的值。已知(i,j)的位置在黄色框的左上角,紫色框的右下角,所以区间1的值可以用s[x][y]表示,区间2的值可以用s[x][j-1]表示(同列不同行),区间3的值可以用s[i-1][y]表示(同行不同列),区间4的值可以用s[i-1][j-1]表示(不同行也不同列,是在(i,j)的对角)。然后就可得到此公式了。
代码例子
#include <bits/stdc++.h>
using namespace std;
int a[10][10],s[10][10],sum; //定义
int main() {
for (int i=1;i<=4;i++) { //循环输入(行)
for (int j=1;j<=4;j++) { //循环输入(列)
cin>>a[i][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<=4-2+1;i++) { //循环输出(行)
for (int j=1;j<=4-2+1;j++) {//循环输出(列),这里的4-2+1代表的是在4*4的大区间中,每行每列最靠后和靠下的2*2区间的左上角坐标
sum=s[i+2-1][j+2-1]-(s[i-1][j+2-1]+s[i+2-1][j-1]-s[i-1][j-1]);
//i+2-1和j+2-1分别指要求的区间的坐标x和y,也就是说在这个循环里,i+2-1指x,j+2-1指y,按公式套入即可得
cout<<sum<<" "; //输出以i和j作左上角坐标,以x和y作右下角坐标的2*2区间的区间和
}
cout<<endl;
}
return 0;
}
//输入:1 2 3 4 输出:5 7 9
// 1 1 1 1 3 3 2
// 0 1 0 0 3 3 2
// 1 1 1 1
前缀和及区间和例题
P8218 求区间和
题目描述
给定由 n 个正整数组成的序列 a1,a2,⋯,an 和 m 个区间 [li,ri],分别求这 m 个区间的区间和。
输入格式
第一行包含一个正整数 n,表示序列的长度。
第二行包含 n 个正整数 a1,a2,⋯,an。
第三行包含一个正整数 m,表示区间的数量。
接下来 m 行,每行包含两个正整数 li,ri,满足 1≤li≤ri≤n。
输出格式
共 m 行,其中第 i 行包含一个正整数,表示第 i 组答案的询问。
输入输出样例
输入 #1复制
4 4 3 2 1 2 1 4 2 3
输出 #1复制
10 5
说明/提示
样例解释
第 1 到第 4 个数加起来和为 10。第 2 个数到第 3 个数加起来和为 5。
数据范围
对于 50% 的数据:n,m≤1000;
对于 100% 的数据:1≤n,m≤10^5,1≤ai≤10^4。
分析
这一题是很典型的一维区间和模板题,通过题目描述和求区间和的公式可以很快做出来
代码
#include <bits/stdc++.h> //万能头文件
using namespace std;
int n,m,a[100005],s[100005],l,r; //输入数字总数n,要求的区间和组数m,存储数字的数组a,储存每个数的前缀和的数组s,要求区间和的区间开头l和结尾r
int main() {
cin>>n; //输入数组总数n
for (int i=1;i<=n;i++) { //循环输入,一共进行n次循环
cin>>a[i]; //输入数字
s[i]=s[i-1]+a[i]; //利用一维前缀和公式求出每个数的前缀和,并存入数组s中
}
cin>>m; //输入组数m
for (int i=1;i<=m;i++) { //循环输入加计算并输出,一共进行m次循环
cin>>l>>r; //因为后面没有用到它们的地方了,所以不用专门定义数组,用普通变量就可以
cout<<s[r]-s[l-1]<<endl; //利用一维区间和公式计算并输出
}
return 0;
}
P2004 领地选择
题目描述
作为在虚拟世界里统帅千军万马的领袖,小 Z 认为天时、地利、人和三者是缺一不可的,所以,谨慎地选择首都的位置对于小 Z 来说是非常重要的。
首都被认为是一个占地 C×C 的正方形。小 Z 希望你寻找到一个合适的位置,使得首都所占领的位置的土地价值和最高。
输入格式
第一行三个整数 N,M,C,表示地图的宽和长以及首都的边长。
接下来 N 行每行 M 个整数,表示了地图上每个地块的价值。价值可能为负数。
输出格式
一行两个整数 X,Y,表示首都左上角的坐标。
输入输出样例
输入 #1复制
3 4 2 1 2 3 1 -1 9 0 2 2 0 1 1
输出 #1复制
1 2
说明/提示
对于 60% 的数据,N,M≤50。
对于 90% 的数据,N,M≤300。
对于 100% 的数据,1≤N,M≤10^3,1≤C≤min(N,M)。每块地价值的绝对值不超过 32767。
分析
本题要求的是在一个n*m的大区间中,二维区间和最大的c*c区间的左上角坐标并输出。由于本题要用二维前缀和及二维区间和来做,所以本题的代码容易出小错误,要多加注意。
代码
#include <bits/stdc++.h> //万能头文件
using namespace std;
long long n,m,c,x,y,maxx=-1e18; //地图的长n,地图的宽m,首都的边长c,最大区间和的区间左上角坐标x和y,区间和最大值maxx
long long a[1005][1005],s[1005][1005],q;
//用来存储每个地块价值的数组a,前缀和数组s,用来临时存储区间和的变量q
int main() {
cin>>n>>m>>c; //输入长与宽,以及首都的边长
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];
//通过计算得到每个地块的价值前缀和
}
}
for (int i=1;i<=n-c+1;i++) {//输入并计算数据
for (int j=1;j<=m-c+1;j++) {
//这里的n-c+1和m-c+1的含义与代码例子中的相同
q=s[i+c-1][j+c-1]-(s[i-1][j+c-1]+s[i+c-1][j-1]-s[i-1][j-1]);
//既然i+c-1指的是“x”,j+c-1指的是“y”,那么就可以把它们带入公式求二维区间和
if (q>maxx) { //如果新求出来的值比最大值大(打擂台找最值)
maxx=q; //更新最大值
x=i; //同时更新答案x的值
y=j; //y的值也要更新
}
}
}
cout<<x<<" "<<y; //输出答案
return 0;
}
总结来说,前缀和与区间和的原理证明有些麻烦,所以建议可以自己画画图,有助于更好的理解。
1056

被折叠的 条评论
为什么被折叠?



