前缀和

【前缀和】
前缀和是一个数组在某个下标之前的所有数组元素之和(包括此元素)。

接下来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;
} 
posted @ 2021-01-25 19:17  h星宇  阅读(184)  评论(0)    收藏  举报