算法简介:前缀和是一种十分基础的数据结构,它与树状数组功能类似,可以维护可以区间相减的信息。预处理
O
(
n
)
O(n)
O(n),并能做到查询复杂度
O
(
1
)
O(1)
O(1)。不支持修改,若修改须重构整个前缀和数组。
算法实现:我们需要一个前缀和数组
s
s
s与基本数组
a
a
a,定义
s
i
s_i
si为
∑
j
=
1
i
a
j
\sum\limits_{j=1}^{i}{a_j}
j=1∑iaj.
预处理:我们需要先做出整个
s
s
s数组,不难想到递推式:
s
i
=
s
i
−
1
+
a
i
s_i=s_{i-1}+a_i
si=si−1+ai。可以用
O
(
n
)
O(n)
O(n)做好这一操作。
查询:若我们要查询
x
x
x,
y
y
y的区间和,那么我们用
s
y
−
s
x
−
1
s_y-s_{x-1}
sy−sx−1来得到这一答案。
修改:当我们要修改
x
x
x,
y
y
y的区间使其加上
z
z
z,用一重循环把
a
a
a数组加上
z
z
z,在按照预处理的办法重构s数组。
这只是一维前缀和,那我们来考虑一下二维前缀和怎么做。
最主要的差别是预处理,其余差别不大。
考虑一下
s
i
,
j
s_{i,j}
si,j。按照一维前缀和的思路,我们需要旁边的两个,于是我们找到了
s
i
−
1
,
j
s_{i-1,j}
si−1,j以及
s
i
,
j
−
1
s_{i,j-1}
si,j−1
难道递推式就是
s
i
,
j
=
s
i
,
j
−
1
+
s
i
+
1
,
j
+
a
i
,
j
s_{i,j}=s_{i,j-1}+s_{i+1,j}+a_{i,j}
si,j=si,j−1+si+1,j+ai,j吗?肯定不是。当我们画一个图,就会发现有重合。再把重合一看,就是
s
i
−
1
,
j
−
1
s_{i-1,j-1}
si−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}=s_{i,j-1}+s_{i+1,j}-s_{i-1,j-1}+a_{i,j}
si,j=si,j−1+si+1,j−si−1,j−1+ai,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
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}
si,j,k=si−1,j,k+si,j−1,k+si,j,k−1−si−1,j−1,k−si−1,j,k−1−si,j−1,k−1+si−1,j−1,k−1+ai,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);
}