【优选算法专栏】专题四:前缀和(一)

简介: 【优选算法专栏】专题四:前缀和(一)


前缀和

算法原理:

我们先分析一下题目:

题目的意思是求给定区间的和,区间由用户决定。

解法一:

看到这个题目,我们首先想到的是暴力+枚举来解决这个情况。

把所有询问q次的子数组都枚举出来,但是题目给的数据很大,肯定会超时。因为每q次询问都要把整个数组遍历一遍,因此时间复杂度为n ∗ q n*qnq

在此基础上我们可以进行优化:

解法二:

解法二就是我们要介绍的前缀和知识。

前缀和是什么?首先我们要知道一点:前缀和可以快速求出数组中某一个连续区间的和。其次前缀和可以降低时间复杂度,对于本题而言,暴力+枚举时间复杂度达到了n ∗ q n*qnq而利用前缀和进行优化就可降低到q qq;

前缀和分两步:

  1. 预处理一个前缀和数组:
  2. 使用前缀和数组

第一步:

什么是一个前缀和数组?我们要先明确一点,其实前缀和就是一个简单的动态规划。我们首先创一个dp数组(下标数组)。这个数组i位置的值代表从[1,i]区间内所有元素的和。也就是说:

dp[i]表示从[1,i]区间内所有元素的和

接下来我们分析一下递推:

注意数组的下标是从1开始而不是从0开始。

arr数组为原数组,dp[2]的值代表原数组区间[1,2]内元素的和,也就是5,dp[3]的值为原数组前三个数相加的结果,那么dp[4]就是原数组前4个数相加的结果,但是从dp数组的第三个位置开始,我们也可以是dp[2]+上原始第三个位置的值,也就是d p [ 3 ] = d p [ 2 ] + a r r [ 3 ] dp[3]=dp[2]+arr[3]dp[3]=dp[2]+arr[3];依次内推最后一个元素的位置就是d p [ i ] = d p [ i − 1 ] + a r r [ i ] dp[i]=dp[i-1]+arr[i]dp[i]=dp[i1]+arr[i];

d p [ i ] = d p [ i − 1 ] + a r r [ i ] dp[i]=dp[i-1]+arr[i]dp[i]=dp[i1]+arr[i]就是我们要找的递推关系式子,而其实这个式子也可以称为动态规划里面的状态转移方程。

预处理做好后,接下来就是第二步:

我们应该如何使用前缀和数组呢?

我们先分析一下:

现在我们的dp数组里面存放的值分别是从1位置开始到i位置结束该区间所有元素的和。那么我们如何求里面具体某一段的和呢?

其实很简单,就比如我们要求[3,5]区间的和,我们[1,5]的和是知道的,[1.2]的和是知道的,我们用[1,5]区间的和-去[1,2]的和不就是我们所求的吗?

我们抽象出来,l和r,那么[l,r]区间的和就是d p [ r ] − d p [ l − 1 ] dp[r]-dp[l-1]dp[r]dp[l1]

介绍到这,我们一维前缀和知识基本上差不多了,但是还有个小问题,别忘了我们数组下标是从1开始的,为什么不从0开始呢?这其实就是为了防止边界情况。

假设我们要求的区间是[0,2]那么对用我们的递推式:

dp[2]-dp[-1],就会产生越界,这里就需要我们对边界情况做特殊处理。

代码实现:

#include <iostream>
#include<vector>
using namespace std;
int main() 
{
    //1.读入数据
    int n=0,q=0;
    cin>>n>>q;
    vector<long long > arr(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>arr[i];
    }
    //2.预处理出来一个前缀和数组
    vector<long long > dp(n+1);
    for(int i=1;i<=n;i++)
    {
        dp[i]=dp[i-1]+arr[i];
    }
    
    //3.使用前缀和数组
    int l=0,r=0;
    while(q--)
    {
        cin>>l>>r;
        cout<<dp[r]-dp[l-1]<<endl;
    }
    return 0;
}

二维前缀和

算法原理:

有了一维前缀和的经验,接下来我们介绍二维前缀和。

首先还是要预处理一个二维前缀和数组出来:

在预处理之前,我们先分析一下:

首先肯定要弄一个跟原数组大小一样的二维数组,我们的dp二位数组里面放的是某区间的和。那么我们的

dp[i][j]就表示:从[1,1]位置到[i][j]位置,这段区间内所有元素的和。

一维好说,这二维看着和想求出来不简单啊,既然直接求不来,那么我们就去而求其次。

咱们把整个面积分成四块:A,B,C,D

那么我们的d p [ i ] [ j ] = A + B + C + D dp[i][j]=A+B+C+Ddp[i][j]=A+B+C+D;

柿子先挑软的捏,D区域肯定是我们最好求的,D区域对应原数组不就是arr[i];不仅如此,A也可以求,A区域为dp[i-1][j-1];

B和C怎么求呢?

既然不好求那干脆我们不求,我们换个角度看:AB区域和AC区域其实很好求,然后看整体:

我们要求整个面积可以这样求:

( A + B ) + ( A + C ) + D − A (A+B)+(A+C)+D-A(A+B)+(A+C)+DA

分析到这,我们的递推关系式其实已经出来了:

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] + a r r [ i ] [ j ] − d p [ i − 1 ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1]dp[i][j]=dp[i1][j]+dp[i][j1]+arr[i][j]dp[i1][j1]

接下来就是第二步:

如何使用:

其实分析到这,使用二维前缀和数组肯定也是要间接求:

给定区间求[x1,y1]到[x2,y2]区间内的和,也就是求D区域。从左上角绿格子到右下角绿格子的这部分区域的值。D区域可以将整个分为4部分A,B,C,D

那么我们的D可以这样求:

D = A + B + C + D − ( A + B ) − ( A + C ) + A D=A+B+C+D-(A+B)-(A+C)+AD=A+B+C+D(A+B)(A+C)+A

那么我们的递推是也就出来了

d p [ x 2 , y 2 ] − d p [ x 1 − 1 ] [ y 2 ] − d p [ x 2 ] [ y 1 − 1 ] + d p [ x 1 − 1 ] [ y 1 − 1 ] dp[x2,y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]dp[x2,y2]dp[x11][y2]dp[x2][y11]+dp[x11][y11]

代码实现:

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    //1.输入数据
    int n=0,m=0,q=0;
    cin>>n>>m>>q;
    vector<vector<long long>> arr(n+1,vector<long long>(m+1));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        cin>>arr[i][j];
    }
    //2.预处理前缀和数组
    vector<vector<long long>> dp(n+1,vector<long long >(m+1));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1];
    
    //3.使用前缀和数组
    int x1,y1,x2,y2;
    while(q--)
    {
        cin >> x1>> y1>>x2>>y2;
        cout<<dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]<<endl;
    }
    return 0;
}
相关文章
|
3月前
|
算法
【算法】前缀和——二维前缀和模板题
【算法】前缀和——二维前缀和模板题
|
3月前
|
算法
【算法】前缀和——前缀和
【算法】前缀和——前缀和
|
2月前
|
存储 算法 Java
前缀和算法
本文介绍了前缀和及其变种在解决区间求和问题中的应用。首先,一维前缀和可通过预处理数组快速求得任意区间的和。接着,二维前缀和扩展了这一思想,适用于矩阵操作。此外,文章探讨了如何利用前缀和解决诸如“寻找数组中心下标”、“除自身以外数组的乘积”等问题,并进一步讲解了涉及哈希表优化的“和为 K 的子数组”等相关题目。最后,通过实例展示了如何在矩阵中高效计算特定区域的元素之和。文中包含代码示例与图解说明,便于理解。
40 0
前缀和算法
|
1月前
|
人工智能 算法 C++
一篇带你速通前缀和算法(C/C++)
一篇带你速通前缀和算法(C/C++)
|
4月前
|
人工智能 算法 JavaScript
【算法】前缀和与差分
算法学习——前缀和与差分(含一维和二维)
50 4
【算法】前缀和与差分
|
3月前
|
算法 C++
【算法】前缀和算法——和可被K整除的子数组
【算法】前缀和算法——和可被K整除的子数组
|
3月前
|
算法
【算法】前缀和算法——和为k的子数组之和
【算法】前缀和算法——和为k的子数组之和
|
3月前
|
算法
【算法】前缀和——除自身以外数组的乘积
【算法】前缀和——除自身以外数组的乘积
|
3月前
|
算法
【算法】前缀和——寻找数组的中心下标
【算法】前缀和——寻找数组的中心下标
|
5月前
|
机器学习/深度学习 算法 测试技术
【算法优选】 动态规划之子数组、子串系列——壹
【算法优选】 动态规划之子数组、子串系列——壹