前缀和总结

考虑这样一个题目:

给定一个长度为N的数组,有Q个询问,每次询问这个数组中一段连续区间的和。
实例: [ 4 , 5 , 6 , − 2 , 3 , 10 ] [4, 5, 6, -2, 3, 10] [4,5,6,2,3,10] 询问第2到第5个数字之和等于12。

思考:

如果按照题目描述进行模拟,那么每个询问都需要使用询问进行求和,速度太慢。
我们可以使用前缀和进行加速。

一维前缀和:

我们假设输入的数组名为 d a t a data data

定义前缀和数组 s u m sum sum,前缀和数组中的每个元素 s u m [ i ] sum[i] sum[i]等于 d a t a data data数组前 i i i个元素之和,即 s u m [ i ] = d a t a [ 1 ] + d a t a [ 2 ] + ⋯ + d a t a [ i ] sum[i]=data[1]+data[2]+\cdots+data[i] sum[i]=data[1]+data[2]++data[i]

可以得到一个更快的递推式: s u m [ i ] = s u m [ i − 1 ] + d a t a [ i ] sum[i]=sum[i-1]+data[i] sum[i]=sum[i1]+data[i],其中 s u m [ 0 ] = 0 sum[0]=0 sum[0]=0,因此可以在 O ( N ) O(N) O(N)的时间复杂度内计算出前缀和数组。

对于原问题中的一个询问,假设需要计算第 a a a个数到第 b b b个数之和,那么有:
d a t a [ a ] + d a t a [ a + 1 ] + ⋯ + d a t a [ b ] = ( d a t a [ 1 ] + d a t a [ 2 ] + ⋯ + d a t a [ b ] ) − ( d a t a [ 1 ] + d a t a [ 2 ] + ⋯ + d a t a [ a − 1 ] ) = s u m [ b ] − s u m [ a − 1 ] data[a]+data[a+1]+\cdots+data[b] =(data[1]+data[2]+\cdots+data[b])-(data[1]+data[2]+\cdots+data[a-1]) =sum[b]-sum[a-1] data[a]+data[a+1]++data[b]=(data[1]+data[2]++data[b])(data[1]+data[2]++data[a1])=sum[b]sum[a1]

可以发现,上面的式子在 a = 1 a=1 a=1等边界情况也是成立的。

因此原数组中一段区间的和等于前缀和数组中两个数的差,用这种方式我们可以 O ( 1 ) O(1) O(1)计算出每个询问的答案。

模版题

前缀和

C++代码
#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int a[N], s[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);

    for (int i = 1; i <= n; i++)
        s[i] = s[i - 1] + a[i];// 前缀和的初始化

    while (m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", s[r] - s[l - 1]);// 区间和的计算
    }

    return 0;
}
考虑扩展题目:

给定一个大小为 N × M N \times M N×M的整数矩阵,有 Q Q Q个询问,每次询问这个矩阵的一个子矩阵的和。
示例(假设行坐标往下增长,列坐标往右增长):
在这里插入图片描述
假设某个询问上图中涂色的子矩阵,那么答案为41 。

二维前缀和:

二维前缀和即是将一维前缀和扩展到了二维的情况。
依然假设上图中输入的矩阵名为 d a t a data data
定义二维前缀和 s u m sum sum,其中 s u m [ i ] [ j ] sum[i][j] sum[i][j] d a t a data data矩阵左上角的部分和,如下图:
在这里插入图片描述
上图中蓝色部分就是 s u m [ 3 ] [ 3 ] sum[3][3] sum[3][3]所包含的 d a t a data data之和。

可以得到递推式:
s u m [ i ] [ j ] = s u m [ i − 1 ] [ j ] + s u m [ i ] [ j − 1 ] − s u m [ i − 1 ] [ j − 1 ] + d a t a [ i ] [ j ] sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+data[i][j] sum[i][j]=sum[i1][j]+sum[i][j1]sum[i1][j1]+data[i][j],其原理如下图:
在这里插入图片描述
对于超出矩阵的部分和 s u m [ i ] [ j ] sum[i][j] sum[i][j]均设置为 0 0 0

那么,对于计算一个子矩阵的和(假设子矩阵的左上角坐标为 ( a , b ) (a,b) (a,b),右下角的坐标为 ( c , d ) (c,d) (c,d)),使用二维前缀和可直接计算得
s u m [ c ] [ d ] − s u m [ c ] [ b − 1 ] − s u m [ a − 1 ] [ d ] + s u m [ a − 1 ] [ b − 1 ] sum[c][d]-sum[c][b-1]-sum[a-1][d]+sum[a-1][b-1] sum[c][d]sum[c][b1]sum[a1][d]+sum[a1][b1]
其原理如下图:
在这里插入图片描述

模版题

子矩阵的和

C++代码
#include <iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N], s[N][N];

int main()
{
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];// 求前缀和

    while (q--)
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);// 算子矩阵的和
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值