前缀和、差分

目录

1 前缀和

1.1 一维前缀和

1.2 一维前缀和案例

1.3 二维前缀和

1.4 二维前缀和案例

2 差分

2.1 一维差分

2.2 一维差分案例

2.3 二维差分

2.4 二维差分案例


1 前缀和

前缀和指数组或序列中每个位置之前的元素总和。

1.1 一维前缀和

原数组:a1 a2,a3…..an

前缀和:Si=a1+a2…..ai  

求前缀和公式:s[i]=s[i-1]+a[i] ,s[0]=0;

序号i

0

1

2

3

4

5

原数组

a[6]

0

3

5

8

2

7

前缀和

b[6]

0

3

8

16

18

25

作用:求原数组[l,r]的和sum

使用原数组计算,则需要从下标l到下标r遍历原数组求和:

Sum=a[l]+a[l+1]…..+a[r]

使用前缀和计算:sum=b[r]-b[l-1]

1.2 一维前缀和案例

描述

输入n个整数,再输入m个区间,每个区间的起点下标L,终点下标R。对于每个区间,输出n个整数中从下标L到下标R的区间和。

输入描述

第一行包括两个整数nm。(1≤n,m≤100000

第二行包括n个整数。(1≤整数≤100)接下来有m行,每行包含两个整数LR,表示区间范围。(0<L≤R≤n

输出描述

输出有m行,每行一个整数,表示一个区间和。

样例输入

10 3
2 1 3 6 4 20 15 10 4 11
3 7
1 9
5 8

样例输出

48
65
49

代码实现

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n, m ;
    cin >> n >> m;
    int a[n+1]={} , b[n+1]={} ,c[m+1][2]={},sum[m+1]={};
    for(int i = 1;i <= n;i++)
    {
        cin >> a[i];
        b[i]=b[i-1] + a[i];
    }
    for(int i = 1;i <=m; i++)
    {
        cin >>c[i][0] >>c[i][1];
        sum[i]=b[c[i][1]]-b[c[i][0]-1];
        cout<< sum[i] << endl;
    }
    
	return 0;
}

1.3 二维前缀和

一维前缀和是解决线性数组在某个区间内元素之和,二维前缀和是在平面问题上,放在二维数组中求矩阵的和。

原数组:a[1][1],a[1][2]……a[i][j]

a[i][j]

j=0

j=1

j=2

j=3

j=4

i=0

0

0

0

0

0

i=1

0

3

4

5

6

i=2

0

2

6

7

1

i=3

0

9

6

3

6

求前缀和:

s[i][j]

j=0

j=1

j=2

j=3

j=4

i=0

0

0

0

0

0

i=1

0

3

7

12

18

i=2

0

5

15

27

34

i=3

0

14

30

45

58

S[i][j]推导过程:

(1)i=0j=0 前缀和默认为0

(2)s[1][1]=3s[1][2]=7s[1][3]=12s[1][4]=18;

      推导过程:s[1][4]=s[1][3]+s[0][4]-s[0][3]+a[1][4]=12+0-0+6=18

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

(3)s[2][1]=5,s[2][2]=15

       s[2][2]=s[1][2]+s[2][1]-s[1][1]+a[2][2]=7+5-3+6=15

 

    求从[2,2]到[3,4]的面积= s[3][4]-s[3][1]-s[1][4]+s[1][2]

   即从[x1,y1]到[x2,y2][]的面积=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];

  

1.4 二维前缀和案例

  

描述

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

输入描述

第一行包含三个整数 nmq

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

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

输出描述

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

样例输入

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≤n,m≤1000,1≤q≤200000,1≤x1≤x2≤n,1≤y1≤y2≤m,−1000≤矩阵内元素的值≤1000

代码实现

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n, m ,q;
    cin >> n >> m >> q;
    int a[n+1][m+1]={} ,sum[n+1][m+1]={},b[q+1][4]={},bj[q+1]={};
    for (int i=1; i <=n ;i++)
    {
        for (int j=1; j <=m; j++)
        {
            cin >> a[i][j];
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
        }
    }
    for (int i=1 ;i <=q; i++)
    {
        for (int j=0; j<4; j++)
        {
            cin>>b[i][j];
        }
        int x1=b[i][0];
        int y1=b[i][1];
        int x2=b[i][2];
        int y2=b[i][3];
         bj[i]=sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
        cout<< bj[i]<<endl;
    }
	return 0;
}

2 差分

2.1 一维差分

差分相当于前缀和的逆运算:

原数组:  a[1]a[2]…..a[n]

差分数组:b[1]b[2]…..b[n]

b[n]=a[n]-a[n-1]

求前缀和公式:s[i]=s[i-1]+a[i] ,s[0]=0;

序号

i

0

1

2

3

4

5

原数组

a[6]

0

3

5

8

2

7

差分数组

b[6]

0

3

2

3

-6

-5

作用:求原数组[L,R]的数全部加上c ,即a[L]+ca[L+1]+c, a[R]+c,可以通过操作差分数组进行计算: 即,b[L]+cb[R+1]-c

1)原数组a[1,3]区间加10,得到新数组c

序号

i

0

1

2

3

4

5

原数组

a[6]

0

3

5

8

2

7

新组

c[6]

0

13

15

18

2

7

2c[6]对应差分数组d

序号

i

0

1

2

3

4

5

新组

c[6]

0

13

15

18

2

7

差分数组

d[6]

0

13

2

3

-16

-5

(3)新的差分数组d与原差分数组b的区别:d[1]-b[1]=10 ,d[4]-b[4]=10

因此:如果a[1,3]区间加10,对应的差分数组b的变化:b[1]+10b[10]-10

解题过程:

(1)初始化原数组a

(2)求原数组a的差分数组b

(3)求原数组[L,R]的数全部加上c,可以直接操作差分数组b:b[L]+cb[R+1]-c

(4)根据差分数组b ,求对应的前缀和数组。

2.2 一维差分案例

描述

在一个桌子上摆放了 n 个杯子,每个杯子中有一定量的水。小 A 同学负责向杯子中倒水,他总共倒了 k 次,每次会向从第 L 个杯子到第 R 个杯子中添加 P 毫升的水(注意:水只可能增加,不可能减少)。请问小 A 同学倒了 k 次水之后, n 个杯子每个杯子有多少毫升的水。

输入描述

第一行包含两个整数 n k

第二行包含 n 个整数,表示一开始每个杯子中水的毫升数。

接下来 k 行,每行包含三个整数 LRP表示一次操作。

输出描述

共一行,包含 n 个整数,表示最终 n 个杯子每个杯子有多少毫升的水。

样例输入

8 3
1 2 10 8 1 5 1 1
7 8 12
1 8 4
2 3 12

样例输出

5 18 26 12 5 9 17 17

数据范围与提示

1≤n,k≤100000

1≤L≤R≤n1≤L≤R≤n,0≤P≤10000≤P≤1000

杯子中水的初始量在 [0,1000][0,1000] 的范围内。

本题数据上保证所有的杯子在加水之后,水量值任然在 int 范围内。

代码实现

#include<bits/stdc++.h>
using namespace std;
int a[100001]={}, b[100001]={} ,c[100001]={}, n, k;//a[]是原数组,b[]差分数组
int main(){
	cin >> n >> k;
    for(int i = 1;i <= n;i++)
    {
        cin >> a[i]; //是原数组
        b[i] = a[i] - a[i-1]; //原数组的差分数组
    }
    for(int i = 1; i <= k; i++)
    {
        int l, r, p;
        cin >> l >> r >> p;
        //原数组l~r 加p,相当于时直接操作差分数组
        b[l] = b[l] +p; 
        b[r+1] = b[r+1] -p;
    }
     for(int i = 1;i <= n;i++)
    {
         c[i]=b[i] + c[i-1];//求差分数组的前缀和数组即为原数组
        cout <<c[i] <<" ";
    }
	return 0;
}

2.3 二维差分

原数组:a[1][1],a[1][2]……a[i][j]

a[i][j]

j=0

j=1

j=2

j=3

j=4

i=0

0

0

0

0

0

i=1

0

3

4

5

6

i=2

0

2

6

7

1

i=3

0

9

6

3

6

差分数组:b[1][1],b[1][2]……b[i][j]

b[i][j]

j=0

j=1

j=2

j=3

j=4

i=0

0

0

0

0

0

i=1

0

3

1

1

1

i=2

0

-1

3

0

-7

i=3

0

7

-7

-4

9

b[i][j]推导过程:b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]

1i=0j=0 前缀和默认为0

b[1][1]=3b[1][2]=1b[1][3]=1b[1][4]=1;

推导过程:b[1][4]=a[1][4] -a[0][4]-a[1][3] +s[0][0] =6-5-0+0=6

2s[2][1]=2s[2][2]=15

s[2][2]=a[2][2]-a[1][2]-a[2][1]+a[1][1]=6-4-2+3=3

差分数组应用 :求原数组[1,2] [2,3]直接的全部数加上10 ,得到新数组c

通过差分数组b计算推导过程:

(1)a对应差分数组b

(2)c对应差分数组d

3)新的差分数组d与原差分数组b的区别:

d[1][2]-b[1][2]=10 ,d[x1][y1]=b[x1][y1]+c;

d[3][2]-b[3][2]=-10,d[x2+1][y1]=b[x2+1][y1]-c;

d[1][4]-b[1][4]=-10,d[x1][y2+1]=b[x1][y1+2]-c;

d[2][3]-b[2][3]=10,d[x2+1][y2+1]=b[x2+1][y2+1]+c;

4)根据差分数组d,求前缀和数组s,即与原数组a [1,2] [3,4]直接的全部数加上10后的数组c 相同

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

解题过程:

(1)初始化原数组a

(2)求原数组a的差分数组b

(3)求原数组[x1,y1][x2,y2]的数全部加上c,可以直接操作差分数组b:

         d[x1][y1]=b[x1][y1]+c;

         d[x2][y1]=b[x2+1][y1]-c;

        d[x1][y2]=b[x1][y2+1]-c;

         d[x2][y2]= d[x2+1][y2+1]=b[x2+1][y2+1]+c;

(4)根据差分数组b ,求对应的前缀和数组。

2.4 二维差分案例

描述

有一个 n×n 大小的方阵,矩阵中的初始值为 0 ,有 k 次操作,每次会将矩阵中以 x1, y1 x2 , y2 为左上角和右下角的子矩阵中的每个数加 1。请输出经过 k 次加数操作后,方阵中每个元素的值。

输入描述

1 行有两个整数 n k。(n,k ≤ 1000

接下来 k 行,每行有 4 个整数 x1 , y1 x2 , y2,两点坐标均在方阵范围内,且左上角的坐标右下角的坐标。

输出描述

输出经过 k 次操作后,方阵中每个元素的值。

样例输入

5 3
2 2 3 3
3 3 5 5
1 2 1 4

样例输出

0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1

代码实现

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n, k , b[1001][1001]={},s[1001][1001]={},q[1001][4]={};
    cin >> n >> k ;
    for (int i=1 ;i <=k; i++)
    {
        for (int j=0; j<4; j++)
        {
            cin>>q[i][j];
        }
        int x1=q[i][0];
        int y1=q[i][1];
        int x2=q[i][2];
        int y2=q[i][3];
         b[x1][y1]+=1;
         b[x1][y2+1]-=1;
         b[x2+1][y1]-=1;
         b[x2+1][y2+1]+=1;
    }
    
    for (int i=1; i <=n ;i++)
    {
        for (int j=1; j <=n; j++)
        {
            s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+b[i][j];
            cout << s[i][j] <<" ";
        }
          cout<<endl;
    }

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值