前缀和与差分

本文介绍了前缀和和差分在处理数组问题中的重要性,包括一维和二维的定义、计算方法以及它们在提高查询效率和解决编程挑战(如4789和736题)中的应用。通过使用前缀和,可以将复杂查询的时间复杂度降低至O(n+m)。
摘要由CSDN通过智能技术生成

一、一维前缀和

前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,合理的使用前缀和与差 分,可以将某些复杂的问题简单化。

前缀和可以简化代码,使时间复杂度降低。

比如:

输入一个长度为 n 的整数序列。接下来再输入 m 个询问,每个询问输入一对 l , r 。对于每个询问,输 出原序列中从第 l 个数到第 r 个数的和。

暴力做法:对于m次询问,从 l 到 r 累加求和。时间复杂度为O(m*n) 如果 n 和 m 的数据量稍微大一点就有可能超时,而我们如果使用前缀和的方法来做的话就能够将时 间复杂度降到 O(n + m) ,大大提高了运算效率。

前缀和做法:先预处理出前缀和数组,再进行m次查询,时间复杂度为O(m+n)

int a[N];
int sum[N]; // sum[i] 表示从数组a的前i项和,sum[i]=a[1]+a[2]+...+a[i];
sum[0]=a[0];
for (int i = 1; i <= N; i++)
{
    sum[i] = sum[i - 1] + a[i];
}
while (m--)
{
    int l, r;
    cin >> l >> r;
    cout << sum[r] - sum[l - 1] << "\n"; // 前r项减去前l-1项
}

所用到的公式就是:

sum[r] = a[1] + a[2] +...+ a[l - 1] + a[l] + a[l + 1] + ... + a[r];
sum[l - 1] = a[1] + a[2] + ... + a[l - 1];
所以[l,r]的和为:sum[r] - s[l - 1] = a[l] + a[l + 1] + ... + a[r];

 二、二维前缀和

对于二维数组s,s[i][j]表示从左上角(1,1)到右下角(i,j)的整个的和

要求这样的一个和,我们可以用双重循环来实现暴力解决,也可以利用推导公式来解决;

下面推导一下:

原二维数组就看作是一个大的矩阵,我们要求的前缀和就相当于是一个小矩阵的面积,我们只需要用大矩阵的面积减去剩余部分的面积就可以得到答案;

图示:

由此我们可以得到:

蓝色矩形面积 s[i][j] = 绿色面积 s[i - 1][j] + 紫色面积 s[i][j - 1] - 重复加的红色的面积 s[i - 1][j - 1] + 小方块的面积 a[i][j] ;

所以我们得到前缀和公式:

s[i][j] = s[i - 1][j] + s[i][j - 1] + a[i] [j] - s[i - 1][j - 1]

继续推广,当我们要求以(x1,y1)(左上角)与(x2,y2)(右下角)为端点的矩阵和的时候,就有:

S=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]

三、一维差分

(I)、差分实际上就相当于是前缀和的逆运算

(II)、例如:

给定区间 [l, r ] ,让我们把 a 数组中的 [l, r] 区间中的每一个数都加上 c ,即 a[l] + c , a[l + 1] + c , a[l + 2] + c ...... a[r] + c ;

(1).暴力做法:

for 循环 l 到 r 区间,时间复杂度 O(n) . 如果我们需要对原数组执行 m 次这样的操作,时间复杂度就会变成 O(n * m)

(2).考虑高效的差分做法 :

sum数组是a数组的前缀和数组,对a数组的 a[i] 的修改,会影响到sum数组中从 sum[i] 及往后的 每一个数。

(III)、一维差分结论:

给sum数组中的 [ l, r] 区间中的每一个数都加上 c ,只需对差分数组 a 做 

a[l] + = c; a[r+1] - = c; //时间复杂度为O(1)

这个操作可以和看成是先全部或局部做变化,然后再把不需要变的那一部分变回去,这里只是为了便于理解二维数组的差分,有时一维数组的差分可以直接对  l  到  r  的值进行改变。 

四、二维差分

(I)二维差分其实可以理解为对二维数组局部做变化,对于一些简单的可以直接用双重循环改变,但是,有些时候这样会很费时。对于二维数组的差分我们通常用下面的方法:

d[x1][y1] += v;
d[x2 + 1][y1] -= v;
d[x1][y2 + 1] -= v;
d[x2 + 1][y2 + 1] += v;

(II)对二维数组差分的剖析:

五、下面找一个题来练一下

(I)、一维前缀和

(1).题目链接:4789. 前缀和序列 - AcWing题库

(做这类题时需要注意下标的对应关系)

(2).代码:


#include <bits/stdc++.h>
using namespace std;
#define int long long
int x, y, z;
void PrintSum(int *a, int *sum, int n)
{
    int i;
    sum[1] = a[1];
    for (i = 2; i <= n; i++)
        sum[i] = sum[i - 1] + a[i];
    cout << sum[z] - sum[y - 1] << endl;
}
signed main()
{
    int n, i, j;
    cin >> n;
    int arr[n + 1], brr[n + 1];
    for (i = 1; i <= n; i++)
    {
        cin >> arr[i];
        brr[i] = arr[i];
    }
    arr[0] = 0;
    brr[0] = 0;
    sort(brr + 1, brr + n + 1);
    int m;
    cin >> m;
    while (m--)
    {
        int sum[n];
        sum[0] = 0;
        cin >> x >> y >> z;
        if (x == 1)
            PrintSum(arr, sum, n);
        else if (x == 2)
            PrintSum(brr, sum, n);
    }
    return 0;
}

(II)、一维差分

(1).题目链接:736. 安迪种树 - AcWing题库

(2).代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
    int n, q, i, j;
    cin >> n >> q;
    int arr[n + 1] = {0};
    while (q--)
    {
        int l, r;
        cin >> l >> r;
        for (i = l; i <= r; i++)
            arr[i] += 1;
    }
    for (i = 1; i <= n; i++)
        cout << arr[i] << " ";
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值