一维前缀和
一维前缀和的定义
一维前缀和的原理很简单,就是将第 i i i 个元素及其之前的所有元素之和预先处理好,方便以后 O ( 1 ) O(1) O(1) 查找的一种预处理算法。
一维前缀和的应用
洛谷里有板子题:P8218
讲解都已经写在代码里了。。。
#include <bits/stdc++.h>
using namespace std;
#define ULL unsigned long long
#define INT __int128
#define tbl ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n,m,a[114514];
int main()
{
// 一维前缀和:z[i]=a[i]+a[i-1]+a[i-2]+…+a[2]+a[1]=z[i-1]+a[i] (用于预处理,降低时间复杂度)
tbl;
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i],a[i]+=a[i-1];//输入a[i]并直接将其前缀和
cin>>m;
for(int i=1;i<=m;++i){
int x,y;
cin>>x>>y;//读入区间
cout<<a[y]-a[x-1]<<endl;
// a[y]表示的区间为 1 部分,a[x-1]表示的区间为 2 部分,相减可得部分 3
// 111111111100000
// 222220000000000
// 000001111100000
// 因为它是[x,y](左闭右闭 )区间,所以应该是a[y]-a[x-1]
// 试想:如果它是(x,y](左开有闭)或[x,y)(左闭右开)的区间,有应该是哪两数相减呢?
}
return 0;
}
其他也没什么可说的了。。。那就下一个吧。。。
二维前缀和
二维前缀和的定义
二维前缀和就有些难理解了,具体定义如下:
建立在一维前缀和之上,我们要求一个矩阵内一个任意的子矩阵的数的和,我们就可以用二维前缀和。二维前缀和的状态和一维前缀和差不多,只不过我们多加了一维,
f
(
i
,
j
)
f(i,j)
f(i,j) 表示
(
1
,
1
)
(1,1)
(1,1) 这个点与
(
i
,
j
)
(i,j)
(i,j) 这个点两个点分别为左上角和右下角所组成的矩阵内的数的和。
我先把状态转移方程告诉大家:
f
(
i
,
j
)
=
f
(
i
−
1
,
j
)
+
f
(
j
−
1
,
i
)
−
f
(
i
−
1
,
j
−
1
)
+
v
a
l
i
,
j
f(i,j)=f(i-1,j)+f(j-1,i)-f(i-1,j-1)+{val}_{i,j}
f(i,j)=f(i−1,j)+f(j−1,i)−f(i−1,j−1)+vali,j
v
a
l
i
,
j
{val}_{i,j}
vali,j 表示点
(
i
,
j
)
(i,j)
(i,j) 的值
具体该如何实现呢?
我们可以手动模拟一下。。。
一开始是这样的:
f
(
i
,
j
)
f(i,j)
f(i,j) 为
0
0
0,即图中所有格子都是白色的。
我们第一个加的是
f
(
i
,
j
−
1
)
f(i,j-1)
f(i,j−1) 所以我们将
(
1
,
1
)
(1,1)
(1,1)~
(
i
,
j
−
1
)
(i,j−1)
(i,j−1) 染成黄色,表示加过一次。如上图所示。
此后再加上
f
(
i
−
1
,
j
)
f(i−1,j)
f(i−1,j),但是我们发现两次染色中有一部分是重复染色的。即:
(
1
,
1
)
(1,1)
(1,1)~
(
i
−
1
,
j
−
1
)
(i−1,j−1)
(i−1,j−1),它们被染过两次色,在上图中我们将这一块区域染成棕色。
既然重复计算了,那我们就剪掉,即减去
f
(
i
−
1
,
j
−
1
)
f(i−1,j−1)
f(i−1,j−1),此时
(
1
,
1
)
(1,1)
(1,1)~
(
i
−
1
,
j
−
1
)
(i−1,j−1)
(i−1,j−1) 又回归了黄色(即只染过一次色)。
我们发现:只需再把
(
i
,
j
)
(i,j)
(i,j) 这一格染成黄色(即再加上
v
a
l
i
,
j
{val}_{i,j}
vali,j),便大功告成了!
二维前缀和的应用
二维前缀和在洛谷没有板子题,所以只能自己造一题了。(悲
题目:
有一个 n n n 行 m m m 列的二维数组,并由 k k k 次提问,每次提问给出左上角的坐标 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) 和右下角的坐标 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 求这个矩形内的所有元素之和。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ULL unsigned long long
#define INT __int128
#define tbl ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int n,m,q,a[1140][5140];
int main() {
// 二维前缀和:z[i][j]=a[i][j]+a[i][j-1]+a[i][j-2]+…+a[i][2]+a[i][1]+
// a[i-1][j]+a[i-1][j-1]+a[i-1][j-2]+…+a[i-1][2]+a[i-1][1]+
// ……………
// a[1][j]+a[1][j-1]+a[1][j-2]+…+a[1][2]+a[1][1]=
// z[i-1][j]+z[i][j-1]-z[i-1][j-1]+a[i][j]
// (用于预处理,降低时间复杂度)
// 稍微有点难理解:
// z[i-1][j]的区间设为 1,z[i][j-1]的区间设为 2,z[i-1][j-1]的区间设为 3,
// 111111110000 222222200000 333333300000
// 111111110000 222222200000 333333300000
// 111111110000 222222200000 333333300000
// 000000000000 222222200000 000000000000
// 000000000000 000000000000 000000000000
// 将一二两图重合,不难发现,重合部分(即为多算的部分)正好为图三
// 所以: 图一 + 图二 - 图三 + 当前节点的值 = 当前节点的前缀和
// z[i-1][j] + z[i][j-1] - z[i-1][j-1] + a[i][j] = z[i][j]
tbl;
cin>>n>>m;
for(int i=1; i<=n; ++i)
for(int j=1; j<=m; ++j)
cin>>a[i][j],a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];//输入a[i][j]并直接将其前缀和
cin>>q;
for(int i=1; i<=m; ++i) {
int x1,y1,x2,y2;
cin>>x1>>y1>>x1>>y2;//读入区间
cout<<a[x2][y2]-a[x1][y2]-a[x2][y1]+a[x1][y1]<<endl;
// z[x1][y2]的区间设为 1,z[x2][y1]的区间设为 2,z[x1][]的区间设为 3,
// 111110000000 222222222000 333330000000
// 111110000000 222222222000 333330000000
// 111110000000 000000000000 000000000000
// 111110000000 000000000000 000000000000
// 000000000000 000000000000 000000000000
// 将一二两图重合,和之前一样,重合部分(即为多算的部分)正好为图三
// 所以: 大区域(即被减区间) - 图一 - 图二 + 图三
// z[x2][y2] - z[x1][y2] - z[x2][y1] + z[x1][y1]
}
return 0;
}
点个赞吧。。。打了好久啊啊啊啊啊啊。。。谢谢各位大牛神犇!!!