算法学习笔记(5)——前缀和与差分

前缀和与差分

一维前缀和

前缀和可以用于快速计算一个序列的区间和,也有很多问题里不是直接用前缀和,但是借用了前缀和的思想。

\(s[i]\) 表示 \(a[0]+a[1]+\dots+a[i]\) 的和(所以称为前缀和),那么要计算区间 \([l, r]\) 的和,也就是计算 \(a[l]+a[l +1]+\dots+a[r]\),只需要计算 \(s[r] - s[l - 1]\) 就可以了,用 \(O(1)\) 的时间就可以计算出来。

题目链接:AcWing 795. 前缀和

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int a[N], s[N]; // s[i]保存以i结尾的前缀和

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++ ) {
        cin >> a[i];
        s[i] = s[i - 1] + a[i];
    }
    
    while (m -- ) {
        int l, r;
        cin >> l >> r;
        cout << s[r] - s[l - 1] << endl;
    }
    
    return 0;
}

差分

差分是前缀和的逆运算,对于一个数组 \(a\),其差分数组 \(b\) 的每一项都是 \(a[i]\) 和前一项 \(a[i - 1]\) 的差,即 \(b[i] = a[i] - a[i - 1]\) 。和前缀和类似的,也是要留出索引是 \(0\) 的位置 \(b[0]=0\),方便计算。

对数组 \(b\) 求前缀和,可以发现就能得到数组 \(a\),所以它保留了数组 \(a\) 的全部信息。而将数组用其差分数组表示的一个优点就是能在 \(O(1)\) 时间里将数组中连续的一段加上一个数 \(c\)

例如,要将 \(a\) 数组的 \([l, r]\) 全都加上 \(c\)。那么考虑到差分数组 \(b\) 求前缀和的时候就能恢复出 \(a\) 数组,可以发现 \(b[l]\) 前面的数都不用变。而只要将 \(b[l]\) 加上 \(c\),那么 \([l, +\infty]\) 就都加上了 \(c\)(因为从加到 \(b[l]\) 开始就加上了这个 \(c\),后面的所有加出来的 \(a[i]\) 就都比之前多 \(c\) 了)。然后将 \(b[r +1]\) 这个位置减去 \(c\),那么计算前缀和以恢复 \(a\) 的时候,到了 \(a[r + 1]\) 的位置就既没加 \(c\) 也没减 \(c\) 了,也就保证了 \([r + 1, +\infty]\) 是不变的。这样就实现了将 \([l, r]\) 这个区间里的所有数都加上 \(c\)

题目链接:AcWing 797. 差分

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int b[N];

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}

int main()
{
    cin >> n >> m;
    
    int x;
    for (int i = 1; i <= n; i ++ ) cin >> x, insert(i, i, x);
    
    while (m -- ) {
        int l, r, c;
        cin >> l >> r >> c;
        insert(l, r, c);
    }
    
    for (int i = 1; i <= n; i ++ ) b[i] += b[i - 1];
    
    for (int i = 1; i <= n; i ++ ) cout << b[i] << ' ';
    
    return 0;
}
posted @ 2022-12-09 22:04  S!no  阅读(56)  评论(0)    收藏  举报