差分和前缀和


前言


什么是差分和前缀和

有一个数组a,数组a中的元素为( a[1] , a[2] ,a[3] ... a[n]),数组下标从1开始a[0] = 0
有一个数组b,数组b中的元素为(a[1] - a[0] , a[2] - a[1] , a[3] - a[2] ... a[n] - a[n-1])

此时ba差分ab前缀和

总结:
分差:就是该项与前一项的差。例:b[i] = a[i] - a[i-1]
前缀和:就是前n项和 例:a[i] = b[i] + b[i-1] + ... + b[1]


一、前缀和的应用


1.一维前缀和

例题:

有个长度为n数组输入lr ,计算数组第l 个数到第r 个数的和 (lrq次输入)

首先想到的就是for暴力出来,每输入一次l,r就循环数组求从lr之间的和,但q次输入时的复杂度就是O(n²),这里的前缀和可以把复杂度降到O(n)。

算法思路

1.用数组s来存序列的前n项和

for (int i=1; i<=n; i++) s[i] = s[i-1] + a[i];

2.询问l,r区间和用r的前n项和减去l的前n项和

printf("%d\n",s[r]-s[l-1]);

总代码如下

#include<iostream>
#include<cstdio>
using namespace std;

const int N = 1e5 + 10;

int n,m,l,r,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]; //序列的前n项和
    while (m--) //复杂度O(n)
    {
        scanf("%d %d",&l,&r);
        printf("%d\n",s[r]-s[l-1]); //r的前n项和减去l的前n项和
    }
}


2.前缀和矩阵(二维)

例题
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。

对于每个询问输出子矩阵中所有数的和。

输入格式
第一行包含三个整数 n,m,q。

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。

输出格式
共 q 行,每行输出一个询问的结果。

数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
解题思路

1.用二维数组a来存放矩阵中的数值(下标要从1开始),把矩阵的数值看成一个小矩形面积
2.用二维数组s的每个位置来存该位置到(1,1)点所组成的矩形的面积之和。

a数组的存放如下图
在这里插入图片描述

s数组的存放方法:

例如:存放(2,2)位置:(1,1)与(2,2)所组成矩形的面积和

在这里插入图片描述
各矩形面积

橙色矩形:面积为4。
绿色矩形:面积为8。
蓝色矩形:面积为6。
绿色和橙色重叠矩形:面积为1。

(2,2)点的面积大小为:橙色矩形 + 绿色矩形 + 蓝色矩形 - 绿色和橙色重叠矩形
代码实现:

for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
             s[i][j] = s[i][j - 1] + s[i - 1][j] + a[i][j] - s[i - 1][j - 1];
    }

s数组最终的存放如下图
在这里插入图片描述

输出所组成的矩形面积:

例如:(2,2)与 (3,4)所组成的矩形面积为

如下图:
在这里插入图片描述
各矩形面积

橙色矩形:为所求矩形。
蓝色矩形:面积为14。
绿色矩形:面积为6。
蓝色与绿色重叠的矩形:面积为1。
总矩形:面积为41。

可得黄矩形为:橙色矩形 = 总矩形 – 绿色矩形 – 蓝色矩形 + 蓝色与绿色重叠的矩形。
(x1,y1) ->(2,2) (x2,y2) ->(3,4)
代码实现:

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

总代码如下

#include<iostream>
using namespace std;


const int N = 1e3 + 10;
int n,m,q;
int a[N][N],s[N][N];

int main()
{
    cin >> n >> m >> q;
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
            cin >> a[i][j];
    }
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
             s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];
    }
    while (q--)
    {
        int x1,y1,x2,y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] << endl;
    }
    
}


一、差分的应用


1.一维差分

例题:

有个一长度n数组a

输入三个整数 l,r,c,表示将数组中 [l,r] 之间每个数加上 c

请你输出进行完所有操作后的数组。

a[6] = {0,2 2 1 2 1}; //长度为5的数组 2 2 1 2 1
int l = 1,r = 5,c = 1; //下标从1开始处理
a[6] = {0,3,3,2,3,2}; //处理后数组变为 3 3 2 3 2

首先想的方法是用for暴力出来但当n次输入l,r,c时的时间复杂度就是O(n²)。


我们用差分处理:

首先想怎么用数组b来还原数组a

 for (int i=1; i<=n; i++)
        a[i] = b[i] + a[i-1];

如果给数组bl个位置加上c,那么在求数组a时,数组a比原来在l位置以后都会加c

a[1] = 2;
a[2] = 0;
c = 1;

b[1] = b[1] + c = 3;
b[2] = a[2] - a[1] = 0;

a[1] = b[1] + a[0] = 3;// 2 + 0 = 3
a[2] = b[2] + a[1] = 3;// 0 + 3 = 3

理解:
a[i] = b[i] + a[i-1]a[i]时都会加上前一项a[i-1],前一项加c那么后一项也会加c
数列理解:
求前n项和有一项加上了c,那么这项后面的所有项都会比原来多加个c。

但题意是加到r停止

如果给数组br +1个位置减去c,那么在求数组a时,数组a比原来在l+1位置以后都会减c

理解:
a[i] = b[i] + a[i-1]a[i]时都会加上前一项a[i-1],前一项减c那么后一项也会减c
数列理解:
求前n项和有一项减去了c,那么这项后面的所有项都会比原来减个c。

图解如下:

在这里插入图片描述

红色:+ c,绿色:- cr+1后相互抵消达到都加

代码实现如下:

 for (int i=1; i<=n; i++)
        b[i] = a[i] - a[i-1]; //a数组的分差
    while (m--)
    {
        int l,r,c;
        scanf("%d %d %d",&l,&r,&c);
        b[l] += c;
        b[r+1] -= c;
    }  //复杂度为O(n)
    for (int i=1; i<=n; i++)
        a[i] = b[i] + a[i-1]; //用b数组求a数组 l位置开始都加c到r结束

2.差分矩阵(二维)

例题:

输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上 c。

请你将进行完所有操作后的矩阵输出。

输入格式
第一行包含整数 n,m,q。

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。

输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2

解题思路

前缀和矩阵的逆运算,现在是已知前缀和数组a求差分数组b

想一想在前缀和矩阵时是怎么求前缀和的。

代码实现

  for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
             a[i][j] = a[i][j - 1] + a[i - 1][j] - a[i - 1][j - 1] + b[i][j]; //a是前缀和
    }

而求差分就是前缀和的逆运算。

代码实现

for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
        {
            b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]; //b是差分b的前缀和就是a
        }
    }

(x1,y1) 对应(1,1)—— (x2,y2) 对应 (2,2)

在这里插入图片描述

如果我们想给橙色矩形都加上c

操作为:

b[x1][y1] += c;

但是此操作后求前缀和时会对(1,1)到 (3,4)的矩形的都加上c

如果对蓝色矩形减c,那么从(3,1)到(3,4)求前缀和的矩形都会减c
如果对绿色矩形减c,那么从(1,3)到(3,4)求前缀和的矩形都会减c
此时求前缀和只有橙色位置加上了c
但是红色矩形被减去了两个c,红色矩形与(3,3)有关,因此只要给(3,3)点加上c那么(3,4)在求前缀和时也会被加上c

代码实现

b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;

总代码如下

#include<iostream>
#include<cstdio>
using namespace std;

const int N = 1e3 + 10;

int a[N][N],b[N][N];

int main()
{
    int  n,m,q;
    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++)
        {
            b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]; //存差分
        }

    }
    while (q--)
    {
        int x1,y1,x2,y2,c;
        scanf("%d %d %d %d %d",&x1,&y1,&x2,&y2,&c);
        b[x1][y1] += c;
        b[x2+1][y1] -= c;
        b[x1][y2+1] -= c;
        b[x2+1][y2+1] += c;

    }
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
        {
            a[i][j] = b[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1]; //前缀和
        }
    }
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
        {
            printf("%d ",a[i][j]);
        }
        cout << endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值