一、前缀和
1、简介
前缀和也是一个在比赛中比较实用的方法,他能很快得求出一个区间的和,速度为O(1)。
一维的前缀和数组sum[ i ]就是存的是前i个数的总和。根据这个公式我们很容易得到x到y区间的总和就是sum[ y ] - sum[ x ]。
二维的前缀和如sum[ i ][ j ]是指 (i,j)点的左上角的矩阵的各数总和,及0 – i行且0 – j列的和。他可以快速的求出矩阵中任何一个子矩阵的和,如求长为l且右下角顶点为(i,j)的正方形只需sum[ i ][ j ] - sum[ i ][ j - l ] - sum[ i - l ][ j ] + sum[ i - l ][ j - l ]。
2、代码模板
//1.一维的前缀和
for(int i = 1;i <= n;i++)
sum[i] += sum[i-1];
ans = sum[y] - sum[x];
//2.二维的前缀和
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
sum[i][j] += sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1]];
ans = sum[i][j] - sum[i-x][j] - sum[i][j-y] + sum[i-x][j-y];
3、经典例题
1.激光炸弹
思路:用二维的前缀和计算,求得矩阵的前缀和后枚举所有边长为r的情况,求出和的最大值即可。
#include<iostream>
using namespace std;
const int N = 5010;
int f[N][N]; //f为求sum和的二维数组
int n,r,h,l,x,y,w; //n为点数,r为半径,h l为最大的长和宽
int main()
{
cin >> n >> r;
h = l = r; //防止出现r比所有的点的坐标都大(整个地图都能炸上)
for(int i = 0;i < n;i++)
{
cin >> x >> y >> w;
x++,y++; //从(1,1)开始,防止数组边界越界
h = max(h,x); //记录最大的横纵坐标
l = max(l,y);
f[x][y] = w;
}
for(int i = 1;i <= h;i++) //求前缀和
for(int j = 1;j <= l;j++)
f[i][j] += f[i-1][j] + f[i][j-1] - f[i-1][j-1];
int res = 0;
for(int i = r;i <= h;i++) //求任意矩阵的和,枚举所有边长为r的矩阵
for(int j = r;j <= l;j++)
res = max(res,f[i][j] - f[i-r][j] - f[i][j-r] + f[i-r][j-r]);
cout << res << endl; //res记录最大的矩阵和即为答案
return 0;
}
4、用处
快速求一维或二维数组某区间的和
二、差分
1、定义
差分就是数组b对于数组a有b[ 1 ] == a[ i ],b[ i ] = a[ i ] - a[ i-1 ](2 <= i <= n),b就是a的差分。
差分和前缀和相反,在对数组某区间进行操作时十分方便。
2、代码模板
for(int i = 0;i < n;i++)
cin >> a[i];
for(int i = n-1;i > 0;i--) //从后往前循环
a[i] -= a[i-1]; //差分覆盖a数组
3、经典例题
1.增减序列
思路:通过对区间加一减一使得数组的各数相同,就是指使数组的差分2-n位都变成0。对区间[ a,b ]的数加一就等于对数组的差分b[ a ] + 1且b[ b+1 ] - 1。
有了这个性质,我们就选b[ i ]和b[ j ]对i,j区间进行加或减的操作,为了使操作次数最小我们尽量选择一正一负作为i,j区间,这样一边加,一边减使得正负都接近0,操作次数就会减少,所以最少操作次数就是差分中2-n位的所有正数和与负数和的绝对值中的最大值。对于无法修改的数可以用b[ 1 ]或者b[ n+1 ]进行修改,结果数就是对正负数配对完后剩下的数加一。
#include<iostream>
#include<cmath>
using namespace std;
const int N = 100010;
int a[N],n;
long long int pos,neg;
int main()
{
cin >> n;
for(int i = 0;i < n;i++)
cin >> a[i];
for(int i = n-1;i > 0;i--)
a[i] -= a[i-1];
for(int i = 1;i < n;i++)
if(a[i] > 0)
pos += a[i]; //所有正数的和
else
neg -= a[i]; //所有负数的绝对值的和
cout << max(pos,neg) << endl;
cout << abs(pos-neg)+1 << endl;
return 0;
}
2、最高的牛
思路:两头牛能够看见,则说明这两头之间的牛的身高最高为两边的减一,每次输入将中间的区间减一即可,但在输入后先判重,检查之前是否已经减过1了,就不用再次减一了。
#include<iostream>
#include<set>
using namespace std;
const int N = 10010;
int f[N],n,p,h,m;
set<pair<int,int> > judge; //set判重,将两端点a,b压入pair
int main()
{
int a,b;
cin >> n >> p >> h >> m;
f[1] = h; //h为差分数组,此处意思是将原数组所有元素设为最大高度h
for(int i = 0;i < m;i++)
{
cin >> a >> b;
if(a > b) //题目有a > b的情况
swap(a,b);
if(!judge.count({a,b})) //如果之前没输过
{
judge.insert({a,b}); //标记
f[a+1]--; //差分数组的性质,区间[a,b]减一
f[b]++;
}
}
for(int i = 1;i <= n;i++)
{
f[i] += f[i-1]; //求出原数组
cout << f[i] << endl;
}
return 0;
}
4、用处
方便对某区间进行加减操作