一维与二维前缀和(蓝桥杯复习+例题讲解+模板c++)


文章首发于: My Blog 欢迎大佬们前来逛逛

前缀和

前缀和是一种常见的算法,用于快速计算数组中某一段区间的和。前缀和的思想就是预处理出数组中前缀和,然后用后缀和减去前缀和,即可快速计算区间和。

以一维数组为例,设 a i a_i ai 表示数组中第 i i i 个元素的值, s u m i sum_i sumi 表示数组中前 i i i 个元素的和,即: s u m i = ∑ j = 1 i a j sum_i = \sum_{j=1}^{i} a_j sumi=j=1iaj 则对于区间 [ l , r ] [l, r] [l,r] 的和可以表示为: ∑ i = l r a i = s u m r − s u m l − 1 \sum_{i=l}^{r} a_i = sum_r - sum_{l-1} i=lrai=sumrsuml1 这里需要注意的是,我们需要在原数组的前面插入一个值为 0 0 0 的元素,这样才能计算出 s u m 0 sum_0 sum0,也就是前 0 0 0 个元素的和。 以下是一维前缀和的代码实现:

vector<int> a; // 原数组
vector<int> sum(a.size() + 1, 0); // 前缀和数组
// 计算前缀和
for (int i = 1; i <= a.size(); i++) {
    sum[i] = sum[i-1] + a[i-1];
}
// 计算区间和
int l = 3, r = 7;
int ans = sum[r] - sum[l-1]; //[3,7]的区间和

二维前缀和

对于二维数组,我们可以先对每一行进行一维前缀和,然后对每一列进行一维前缀和,即可得到二维前缀和数组。 设 a i , j a_{i,j} ai,j 表示二维数组中第 i i i 行第 j j j 列的元素值, s u m i , j sum_{i,j} sumi,j 表示以 ( i , j ) (i,j) (i,j) 为右下角的矩阵的元素和,则有: s u m i , j = ∑ k = 1 i ∑ l = 1 j a k , l sum_{i,j} = \sum_{k=1}^{i} \sum_{l=1}^{j} a_{k,l} sumi,j=k=1il=1jak,l 则对于矩阵 [ x 1 , y 1 , x 2 , y 2 ] [x1,y1,x2,y2] [x1,y1,x2,y2] 的元素和可以表示为: ∑ i = x 1 x 2 ∑ j = y 1 y 2 a i , j = s u m x 2 , y 2 − s u m x 1 − 1 , y 2 − s u m x 2 , y 1 − 1 + s u m x 1 − 1 , y 1 − 1 \sum_{i=x1}^{x2}\sum_{j=y1}^{y2} a_{i,j} = sum_{x2,y2} - sum_{x1-1,y2} - sum_{x2,y1-1} + sum_{x1-1,y1-1} i=x1x2j=y1y2ai,j=sumx2,y2sumx11,y2sumx2,y11+sumx11,y11 以下是二维前缀和的代码实现:

vector<vector<int>> a; // 原数组
vector<vector<int>> sum(a.size() + 1, vector<int>(a[0].size() + 1, 0)); // 二维前缀和数组
//前缀和的预处理
for (int i=1;i<=a.size();i++){
    for (int j=1;j<=a[0].size();j++){
        sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
    }
}
// 计算矩阵的元素和
int x1 = 2, y1 = 3, x2 = 5, y2 = 7;
int ans = sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1];

总结

前缀和是一种非常实用的算法,可以用于快速计算数组中某一段区间的和。在一维数组中,只需要预处理出前缀和数组即可;在二维数组中,需要先对每一行进行一维前缀和,然后再对每一列进行一维前缀和,得到二维前缀和数组。

3956. 截断数组

3956. 截断数组

题目要求:把一个数组从中间分开两份,使得这个数组被分成三份,其中每一份都是非空的,而且要求每一份的元素的和都相等。

例:

4
1 2 3 3

从下标为2的地方分第一份,下标为3的地方分第二份,因此可以分成如下的三段序列:

1 2,  3 ,  3

其中每一段的和都是相等的,为3。

每一段必须是非空的,如果:

2
0 0

则无解,因为这个序列无法分成三份,因此输出 0

同理,如果分不出三份,使得每一份的和都相等,则也输出0

我们需要得到的是分割的方案数。


一维前缀和的思想:我们统计这个序列的前缀和,以 1 2 3 3为例:

原数组为 nums[4]={1,2,3,3} ;前缀和数组为:presum[4]={1,3,6,9}

因此我们可以发现:如果数组的总和不能被3整除,则它一定不能分成三份,此时直接输出0即可。

如果数组的总和能够被3整除,则还不一定存在方案,例如:3 3,但是它只有两份。

我们可以得出另一个结论,如果数组总和能够被3整除,并且还必须需要分成三份。

因此我们可以计算出 avg=总和的三分之一,则我们必须满足两个临界点:

  1. 找到一个满足 avg*2 的地方,如果找到了这个地方为i, 则后面 i到n 的和一定是avg。
  2. 找到一个满足 avg 的地方,如果找到了这个地方,则我们统计满足这个地方的方案数。

最后如果在 avg*2 的地方加上 avg*1 的方案数,则就是整个数组分成三份的合法方案数,因为后面的avg*3的地方就是整个数组的和,找到了这两个地方就可以确定整个数组一定是合法的。


#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
const int N=1e5+10;
int nums[N],presum[N];
int n;
signed main(){
    cin>>n;
    int sum=0;
    for (int i=1;i<=n;i++){
        cin>>nums[i];
        presum[i]=nums[i]+presum[i-1];
        sum+=nums[i];
    }
    if (sum%3!=0){
        //一定不能分成三份
        cout<<0;
    }
    else{
        int avg=sum/3;
        int js=0;
        int ans=0;
        for (int i=1;i<n;i++){
            //一定要留给第三份一个元素的空间,所以不能取到n
            if (presum[i]==avg*2){
                ans+=js;
            }
            if (presum[i]==avg){
                ++js;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

99. 激光炸弹

99. 激光炸弹 - AcWing题库

题目要求:给你一个矩形区域,这个矩形区域上有几个点,每个点都具有一个值,并且给你一个R,使你在这个矩阵中所有的R*R的正方形中找到具有的值最大的一个正方形的总价值。

这道题就是二维前缀和的应用。

假设我的初始矩阵区域如图所示:

1 0
0 1

则转换为二维前缀和矩阵:

1 1
1 2

如果此时R为1,则表面上我们可以取得 2为最大值,但是其实我们的二维前缀和应该这样计算:

val=sum[i][j]-sum[i-1][j]-sum[i][j-1]+sum[i][j]

val=2-1-1+1=1,则答案为1,根据原二维数组我们也可知,答案为1


因此我们只需要预处理出二维数组的前缀和即可,然后根据所给的R,找到一个最大的区域,使得值最大,统计最大值即可。

需要注意的几个地方:

  1. 可能会出现相同的位置具有多个值,则把他们的值相加即可
  2. 由于没有告诉这个矩形范围是多大,因此每次我们直接枚举到题目要求的极大值即可,即5e3左右
  3. 从可以容纳的最小的 R*R的正方形范围开始,统计每一个R*R的值,然后统计最大值。
#include <iostream>
#include <algorithm>
using namespace std;
const int N=5010;
int nums[N+10][N+10];
int n,r;
int main(){
    cin>>n>>r;
    //r可能会出现一个大值:1e9,但是只需要到N即可
    r=min(r,N);
    for (int i=1;i<=n;i++){
        int x,y,w;
        cin>>x>>y>>w;
        nums[x+1][y+1]+=w;//可能出现相同的位置,值相加
    }
    //预处理前缀和
    for (int i=1;i<=N;i++){
        for (int j=1;j<=N;j++){
            nums[i][j]+=nums[i-1][j]+nums[i][j-1]-nums[i-1][j-1];
        }
    }
    //求出每一个R*R的正方形做具有的最大值
    int ans=0;
    for (int i=r;i<=N;i++){
        for (int j=r;j<=N;j++){
            ans=max(ans,nums[i][j]-nums[i-r][j]-nums[i][j-r]+nums[i-r][j-r]);
        }
    }
    cout<<ans;
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuleo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值