前缀和—基础数据结构 --- luogu P2280 [HNOI2003]激光炸弹&&luogu P5638 【CSGRound2】光骓者的荣耀

题面传送门

算法简介:前缀和是一种十分基础的数据结构,它与树状数组功能类似,可以维护可以区间相减的信息。预处理 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=1iaj.
预处理:我们需要先做出整个 s s s数组,不难想到递推式: s i = s i − 1 + a i s_i=s_{i-1}+a_i si=si1+ai。可以用 O ( n ) O(n) O(n)做好这一操作。
查询:若我们要查询 x x x y y y的区间和,那么我们用 s y − s x − 1 s_y-s_{x-1} sysx1来得到这一答案。
修改:当我们要修改 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} si1,j以及 s i , j − 1 s_{i,j-1} sij1
难道递推式就是 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,j1+si+1,j+ai,j吗?肯定不是。当我们画一个图,就会发现有重合。再把重合一看,就是 s i − 1 , j − 1 s_{i-1,j-1} si1,j1。那么递推式便是 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,j1+si+1,jsi1,j1+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=si1,j,k+si,j1,k+si,j,k1si1,j1,ksi1,j,k1si,j1,k1+si1,j1,k1+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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值