前缀和
【前缀和】
前缀和是一个数组在某个下标之前的所有数组元素之和(包括此元素)。
接下来b[]为前缀和数组,arr[]为原数组
一维前缀和
定义式:\(b[i] =\sum_{j=0}^{i}a[j]\);
递推公式:\(b[0]=0; b[i] = b[i-1]+a[i]\);
初始化O(n):
for(int i = 1; i <= n; i++){
cin >> a[i];
b[i] = b[i-1]+a[i];
}
查询O(1):
cout << b[l] - b[r-1] << endl;
上道例题 AT1894総和
输入两个整数\(n,k\),给出一个长度为\(n\)数列,求这个数列中的所有长度为\(k\)的连续的部分的总和。\(1 \leq k \leq n \leq 10^5,0 \leq a_i \leq 10^8\)。
思路:本题数据范围过大,我们无法使用暴力,我们可以先计算整个数列的前缀和,然后计算数列中的所有长度为\(k\)的连续的部分的总和。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
const int INF = 0x7fffffff;
const int N = 1e5+10;
ll arr[N], n, k, res;
int main(){
cin >> n >> k;
for(int i = 1; i <= n; i++){
cin >> arr[i];
arr[i] += arr[i-1];
}
for(int i = 1; i <= n-k+1; i++){
res += arr[k+i-1] - arr[i-1];
}
cout << res << endl;
return 0;
}
二维前缀和
定义式:\(b[x][y]=\) \(\sum_{i=0}^{x}\sum_{j=0}^{y}a[i][j]\)
递推公式:\(b[x][y]=b[x-1][y]+b[x][y-1]-b[x-1][y-1]+a[x][y]\)
初始化O(nm):
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
b[i][j] = b[i-1][j] + b[i][j-1] - b[i-1][j-1] + a[i][j];
}
}
下面我们来理解为什么二维前缀和的递推公式为\(b[i][j] = b[i-1][j] + b[i][j-1] - b[i-1][j-1] + a[i][j]\)。
| 原数组A | \(2_{1,1}\) | \(3_{1,2}\) | \(4_{1,3}\) | 前缀和B | \(2_{1,1}\) | \(5_{1,2}\) | \(9_{1,3}\) |
|---|---|---|---|---|---|---|---|
| \(7_{2,1}\) | \(4_{2,2}\) | \(8_{2,3}\) | \(9_{2,1}\) | \(16_{2,2}\) | \(28_{2,3}\) | ||
| \(5_{3,1}\) | \(9_{3,2}\) | \(10_{3,3}\) | \(14_{3,1}\) | \(30_{3,2}\) | \(52_{3,3}\) |
现在我们来计算区间\(A[2][2]\)的前缀和:
如果我们直接用递推公式\(B[2][2]=B[2][1]+B[1][2]-A[1][1]+A[2][2]=9+5-2+4=16\);为什么呐?
\(B[2][2]=A[1][1]+A[1][2]+A[2][1]+A[2][2]=2+3+7+4=16\);
\(B[2][1]=A[1][1]+A[2][1]\);
\(B[1][2]=A[1][1]+A[1][2]\);
使用在我们计算\(A[2][2]\)前缀和的时候就可以用\(A[1][2]\)的前缀和加上\(A[2][1]\)的前缀和,再减去多加的\(A[1][1]\),最后加上自己本身的数值。
查询O(1):
sum = b[x2][y2] - b[x2][y1-1] - b[x1-1][y2] + b[x1-1][y1-1];
同样的下面我们来理解二维前缀和的查询代码:
| 原数组A | \(2_{1,1}\) | \(3_{1,2}\) | \(4_{1,3}\) | 前缀和B | \(2_{1,1}\) | \(5_{1,2}\) | \(9_{1,3}\) |
|---|---|---|---|---|---|---|---|
| \(7_{2,1}\) | \(4_{2,2}\) | \(8_{2,3}\) | \(9_{2,1}\) | \(16_{2,2}\) | \(28_{2,3}\) | ||
| \(5_{3,1}\) | \(9_{3,2}\) | \(10_{3,3}\) | \(14_{3,1}\) | \(30_{3,2}\) | \(52_{3,3}\) |
现在我们想求区间\(A[2][3]\)到\(A[3][3]\)的数值之和\(res\):
如果我们直接使用代码\(res=B[3][3]-B[3][2]-B[1][3]+B[1][2]=52-30-9+5=18\);Why?
我们来看\(B[3][3]\)等于区间\(A[1][1]\)到\(A[3][3]\)的和,\(B[2][3]\)等于区间\(A[1][1]\)到\(A[2][3]\)的和
现在我们计算区间\(A[2][3]\)到\(A[3][3]\)的和,就等于我们在区间\(A[1][1]\)到\(A[3][3]\)上删除区间\(A[1][1]\)到\(A[3][2]\)和区间\(A[1][1]\)到\(A[1][3]\),但我们又发现我们多删除了区间\(A[1][1]\)到\(A[1][2]\),所以我们要加上它.
所以\(res=B[3][3]-B[3][2]-B[1][3]+B[1][2]\)。
来检验检验自己是否理解P1719最大加权矩形
有一个\(N\times N\)矩阵,要求矩阵中最大加权矩形,即矩阵的每一个元素都有一权值,权值定义在整数集上。从中找一矩形,矩形大小无限制,是其中包含的所有元素的和最大 。矩阵的每个元素属于\([-127,127]\),\(N\leq 120\).
思路:计算出\(N\times N\)矩阵的前缀和,如果枚举找出一个矩形,这个矩形内包含的所有元素的和最大。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
const int INF = 0x7fffffff;
const int N = 1e5+10;
int MAX = -INF;
int n, m, c, x, y, t, sum[130][130];
int main(){
cin >> n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cin >> t;
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + t;
}
}
for(int x1 = 1; x1 <= n; x1++){
for(int y1 = 1; y1 <= n; y1++){
for(int x2 = 1; x2 <= n; x2++){
for(int y2 = 1; y2 <= n; y2++){
if(x2 < x1 || y2 < y1) continue;
MAX = max(MAX,sum[x2][y2] + sum[x1-1][y1-1] - sum[x2][y1-1] - sum[x1-1][y2]);
}
}
}
}
cout << MAX << endl;
return 0;
}

浙公网安备 33010602011771号