前缀和学习笔记
算法简介:前缀和是一种十分基础的数据结构,它与树状数组功能类似,可以维护可以区间相减的信息。预处理\(O(n)\),并能做到查询复杂度\(O(1)\)。不支持修改,若修改须重构整个前缀和数组。
算法实现:我们需要一个前缀和数组\(s\)与基本数组\(a\),定义\(s_i\)为\(\sum\limits_{j=1}^{i}{a_j}\).
预处理:我们需要先做出整个\(s\)数组,不难想到递推式:\(s_i=s_{i-1}+a_i\)。可以用\(O(n)\)做好这一操作。
查询:若我们要查询\(x\),\(y\)的区间和,那么我们用\(s_y-s_{x-1}\)来得到这一答案。
修改:当我们要修改\(x\),\(y\)的区间使其加上\(z\),用一重循环把\(a\)数组加上\(z\),在按照预处理的办法重构s数组。
这只是一维前缀和,那我们来考虑一下二维前缀和怎么做。
最主要的差别是预处理,其余差别不大。
考虑一下\(s_{i,j}\)。按照一维前缀和的思路,我们需要旁边的两个,于是我们找到了\(s_{i-1,j}\)以及\(s_{i,j-1}\)
难道递推式就是\(s_{i,j}=s_{i,j-1}+s_{i+1,j}+a_{i,j}\)吗?肯定不是。当我们画一个图,就会发现有重合。再把重合一看,就是\(s_{i-1,j-1}\)。那么递推式便是\(s_{i,j}=s_{i,j-1}+s_{i+1,j}-s_{i-1,j-1}+a_{i,j}\)。
有没有感觉像韦恩图?
再想想三维前缀和,按照二维前缀和的思路,发现递推式是\(s_{i,j,k}=s_{i-1,j,k}+s_{i,j-1,k}+s_{i,j,k-1}-s_{i-1,j-1,k}-s_{i-1,j,k-1}-s_{i,j-1,k-1}+s_{i-1,j-1,k-1}+a_{i,j,k}\);虽然很长,但还是韦恩图。
所以,一个数据结构被归纳成一个数学模型。
个人理解:一维前缀和十分简单,用途很广,但无法维护是它的一个弱点,特别是维度高了之后维护十分吃力,所以实用性不如线段树或树状数组。
但前缀和是一切的基础,比如树状数组等都用了前缀和思想。
这道题是一个前缀和裸题,初学者可以练手
代码实现:
#include<cstdio>
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
int n,m;
long long a[1000005],ans=1e20,s[1000005];
inline void read(long long &x) {
int f=1;x=0;
char s=getchar();
while(s<'0'||s>'9') {if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
int main(){
register int i,j;
scanf("%d%d",&n,&m);
n--;
for(i=1;i<=n ;i++) read(a[i]),s[i]=s[i-1]+a[i];
for(i=0;i<=n-m;i++)if(s[n]-s[i+m]+s[i]<ans)ans=s[n]-s[i+m]+s[i];
printf("%lld",ans);
return 0;
}
题面传送门
那这道题只要做好前缀和再枚举就好了。
代码实现:
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n,m,x,y,z,f[5039][5039],ans;
int main(){
register int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d%d%d",&x,&y,&z);
f[x+1][y+1]+=z;
}
for(i=1;i<=5001;i++)for(j=1;j<=5001;j++)f[i][j]+=f[i][j-1]+f[i-1][j]-f[i-1][j-1];
for(i=0;i<=5001-m;i++){
for(j=0;j<=5001-m;j++){
ans=max(ans,f[i+m][j+m]-f[i][j+m]-f[i+m][j]+f[i][j]);
}
}
printf("%d",ans);
}