【数组】 差分

【数组】 差分

前缀和 与 差分

我在前面的两篇博客里面简要介绍了一下一维、二维数组的前缀和的一些知识点,提到前缀和,那很自然地就会提到差分的概念。
首先我们回顾一下前缀和:原数组a[n]和依据原数组构造出的新数组s[n],s[n]是a[n]的前缀和数组
俗话说,有来有往,阴阳交汇,分久必合、合久必分......
总而言之,我们看到了新数组s[n]是原数组a[n]的前缀和数组,但我们都知道,事物之间的影响总是交互的,所以我们必然会想到原数组a[n]是新数组s[n]的什么?只是前缀和数组的原数组吗?

现在让我们先把注意力投向求前缀和数组的公式

s[n] = s[n-1] + a[n];

现在让我们抛开s[n]相对于a[n]前缀和的身份不谈,我们有一个普通s[n],它的每一项s[n]都基于上一向s[n-1]的值 再加上一个常数a[n]。
这个常数a[n]正是数组s[n]与s[n-1]的差值
那么我们变换公式:a[n] = s[n] - s[n-1]。
由此我们得到一个新但旧的数组即原数组,a[n],称为s[n]数组的差分数组

结论:s[n]是原数组a[n]的前缀和数组、a[n]是原数组s[n]的差分数组。

差分数组的用处

差分数组和前缀和数组的用途相似,都是面对以区间为单位进行操作的需求。
差分将对区间进行的增减操作由O(n)优化为O(1)。
以下给出一道题目:

题目描述:
输入一个长度为 n的整数序列。接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r]之间的每个数加上 c。
请你输出进行完所有操作后的序列。

我们需要对一个区间进行加减操作,如果按照朴素思想,我们需要从左端点遍历都右端点,时间复杂度为O(n)。进行m次操作的时间复杂度就是O(mn)。
而对差分数组进行操作,再求一遍前缀和的时间复杂度是O(m+n)

如图,对查分数组的某一个元素+1,则原数组从这个元素开始的元素都会加一。

因此,再让区间外的第一个元素减一就能实现这个区间内的元素加一,以此类推。

{
    #include <iostream>
    #define N 100010
    using namespace std;
    int f[N];
    int s[N];
    int main()
    {   
        int n,m;
        cin >> n >> m;
        for(int i = 1; i <= n; i++)
        {    
            cin >> f[i];
            s[i] = f[i] - f[i-1];
        }
        while(m--)
        {
            int l,r,c;
            cin >> l >> r >> c;
            s[l] += c;
            s[r+1] -= c;
        }
        for(int i =1 ; i <= n; i++)
        {
            f[i] = f[i-1] + s[i];
            cout << f[i] << ' ';
        }
    }
}

二维差分

同样地,二维数组也有差分。其目的仍然是对区间(子矩阵)进行操作。
在一维数组中,我们改变原数组的某一元素,会改变前缀和数组一段区间的值。
同样,在二维数组中,我们改变原数组的某一元素,会改变前缀和数组一个子矩阵的值。
首先,二维数组前缀和的几何意义是,从矩阵左上角到该元素的元素和。

如图中的前缀和数组的(i,j)元素表示的是原数组绿色部分的元素和。
那么我们给这个原数组中元素加上一个1,前缀和数组在下图中的黄色部分都会加上一个1(该元素到矩阵右下角)。

现在我们要让蓝色部分加上一个值X,但显然这样操作仍会对黄色部分加上值X。那么我们再让黄色区域减去这个X即可,同一维的是一个道理,注意重复的地方要加上一个X。

例题
2536. 子矩阵元素加 1

class Solution {
public:
    vector<vector<int>> rangeAddQueries(int n, vector<vector<int>>& queries) {
        vector<vector<int>> tmp(n+1 , vector<int>(n+1,0));
        int m = queries.size();
        for(int i = 0; i < m; i++)
        {
            tmp[queries[i][0]][queries[i][1]]++;
            tmp[queries[i][0]][queries[i][3]+1]--;
            tmp[queries[i][2]+1][queries[i][1]]--;
            tmp[queries[i][2]+1][queries[i][3]+1]++;
        }//对差分数组进行操作
        vector<vector<int>> ans(n , vector<int>(n , 0));
        ans[0][0] = tmp[0][0];
        for(int i = 0; i < n; ++i)
            for(int j = 1; j < n; ++j) tmp[i][j] += tmp[i][j-1];
        for(int i = 1; i < n; ++i)
            for(int j = 0; j < n; ++j) tmp[i][j] += tmp[i-1][j];
        for(int i = 0; i < n; ++i)
            for(int j = 0; j < n; ++j) ans[i][j] = tmp[i][j];
        //由于下标从0开始,所以分布求前缀和要方便一点
        //这种写法不存在重复加的地方,第一个for求一维前缀和,第二个for加上前面行的和。
        return ans;
    }
};  
posted @ 2023-01-16 18:09  瞻鹤  阅读(71)  评论(2编辑  收藏  举报