前缀和 || 一维、二维前缀和

一维数组的前缀和

【模板】前缀和_牛客题霸_牛客网 (nowcoder.com)icon-default.png?t=N7T8https://www.nowcoder.com/practice/acead2f4c28c401889915da98ecdc6bf?tpId=230&tqId=2021480&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

暴力解法:每次输入 l、r 时,找到数组中下标为 l 的数,一直相加到下标为 r 的数,由于查询 q 次,所以时间复杂度为 O(n*q) 。如果 l、r 之间的数有一部分在之前已经算过,相当于重复计算了。

为了避免重复计算,我们可以定义一个长度为 n 的数组 dp,dp[ i ] 保存数组 v 从 0 ~ i 的数的和,也就是前缀和。如图,dp [ i ] 计算的是绿色框内数字的和,即 v[ 1 ] 到 v[ i ] 的和。

在计算 dp [ i ] 时 ,由于 dp[ i -1 ] 记录了 v[ 1 ] 到 v[ i -1 ] 的和,所以我们没必要再次从头到尾遍历 v 数组来计算 dp [ i ] ,我们直接 dp [ i ]  = dp[ i -1 ] + v [ i ]

我们注意到题目中提供的数组是从 1 开始计数的,这是为了避免数组的越界访问。如果从 0 开始计数,那么 i == 0 时,i - 1 = -1,则我们需要特殊处理 dp[ i -1 ] ,避免越界访问;如果从 1 开始计数,我们把 dp[ 0 ] 初始化为 0, i == 1 时,计算 dp[ i -1 ] 就不用特殊处理了。因为是从 1 开始计数的,原本长度为 n 的数组,我们需要设置长度为 n+1 。

当输入 l、r 时,由于 dp[ l ] 计算的是v [ 1 ] ~ v[ l ] 的和, dp[ r ] 计算的是v [ 1 ] ~ v[ r ] 的和,v[ l ] ~ v[ r ] 的和应该为v [ 1 ] ~ v[ r ] 的和 减去 v [ 1 ] ~ v[ l-1 ] 的和,即 dp [ r ] -  dp [ l-1 ] 。

#include <iostream>
using namespace std;
#include<vector>
int main() {
    int n,q;
    cin>>n>>q;
    vector<int> v(n+1);
    for(int i=1;i<n+1;i++)
    {
        cin>>v[i];//输入数组
    }
    vector<long long> dp(n+1);//前缀和
    for(int i=1;i<n+1;i++)
    {
        dp[i]=dp[i-1]+v[i];
    }
    int l,r;
    while(q--)
    {
        cin>>l>>r;
        cout<<dp[r]-dp[l-1]<<endl;
    }
    return 0;
}

二维数组的前缀和

【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com)icon-default.png?t=N7T8https://www.nowcoder.com/practice/99eb8040d116414ea3296467ce81cbbc?tpId=230&tqId=2023819&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

 题目给定的二维数组中,输入 x1,y1,x2,y2,得出子矩阵的和,即下图中绿色框内的和。

 暴力解法依旧是直接遍历给定范围的矩阵,这样的时间复杂度很高,会超时。

受一维数组的前缀和的启发,我们可以创建一个二维数组 dp,dp 中记录了以 vv[ 1 ][ 1 ]为左上角,vv[ i ][ j ]为右下角的矩阵的和。(矩阵从 1 开始计数,同样为了避免越界访问)

假设我们需要计算以 vv[ 1 ][ 1 ]为左上角,vv[ i ][ j ]为右下角的矩阵的和,我们可以把 vv[ i ][ j ]的和切割为求下图中矩阵A、B、C、D的和,但是 B、C的和不好求,因为又需要再遍历一遍数组,我们可以拼凑法,A+B 、A+C 的和已经计算过了,所以可以拼凑为:A+B+C+D = (A+B) + (A+C) - A + D,A+B 为 dp[ i-1 ][ j ],A+C 为 dp[ i ][ j -1 ],A 为 dp [ i-1 ][ j-1 ] ,

dp 矩阵里面的数为 long long 类型,防止多个 int 类型的数相加导致溢出。

dp[ i ][ j ] = dp[ i-1 ][ j ] + dp[ i ][ j-1 ] - dp[ i-1 ][ j-1 ] + vv[ i ][ j ];

计算完二维数组的前缀和后,开始查询 x1, y1, x2, y2 的矩阵的和。

我们需要计算 D 的矩阵的和,我们可以根据前缀和 dp,把 D 先拼凑成 A、B、C、D,由于 B、C 的矩阵和不方便计算,可以转换为便于计算的 A+B、A+C,

所以 D = A + B + C + D - ( A+B ) -( A+C ) + A

long long ret=dp[ x2 ][ y2 ] - dp[ x1-1 ][ y2 ] - dp[ x2 ][ y1-1 ] + dp[ x1-1 ][ y1-1 ];

#include <iostream>
using namespace std;
#include<vector>
int main() {
    int n=0,m=0,q=0;
    cin>>n>>m>>q;
    vector<vector<int>> vv(n+1,vector<int>(m+1));//注意怎么创建二维矩阵
    for(int i=1;i<n+1;i++)
    {
        for(int j=1;j<m+1;j++)
        {
            cin>>vv[i][j];
        }
    }
    vector<vector<long long>> dp(n+1,vector<long long>(m+1));
    for(int i=1;i<n+1;i++)
    {
        for(int j=1;j<m+1;j++)
        {
            dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+vv[i][j];
        }
    }
    int x1=0,x2=0,y1=0,y2=0;
    while(q--)
    {
        cin>>x1>>y1>>x2>>y2;
        long long ret=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
        cout<<ret<<endl;
    }
    return 0;
}
// 64 位输出请用 printf("%lld")

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值