大数据最新【详解】手撕 一维、二维、三维差分数组原理(2),2024年最新程序员面试防坑宝典

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

)

O(m+n^2)

O(m+n2),肯定会 TLE。

**(

1

)

\color{Purple}(1)二维差分的定义

(1)二维差分的定义**



    在一维差分中,原数组a[ ]是从第1个b[1]开始的差分数组 b[ ]的前缀和:a[x]= b[1] + b[2] + ··· + b[x]。  
 




     



在二维差分中,a[ ][ ]是差分数组b[ ][ ]的前缀和,即将原点坐标(1,1)和坐标(i,j)围成的矩阵中,所有的b[ ][ ]相加等于a[ i ][ j ]。我们可以把每个b[][]看成一个小格;在坐标(1,1)(i,j)所围成的范围内,所有小格子加起来的总面积,等于 a[i][j]。如下图中,每个格子的面积是一个 b[ ][ ],例如阴影格子是b[ i ][ j ],它由4个坐标点组成:

(

i

,

j

)

\color{CadetBlue}(i, j)

(i,j)、

(

i

1

,

j

)

\color{CadetBlue}(i - 1, j)

(i−1,j)、

(

i

,

j

1

)

\color{CadetBlue}(i, j - 1)

(i,j−1)、

(

i

1

,

j

1

)

\color{CadetBlue}(i - 1, j - 1)

(i−1,j−1)。坐标点(i, j)的值是 a[ i ][ j ],它等于坐标(1,1)(i,j)所围成的所有格子的总面积 。

在这里插入图片描述

a

[

]

[

]

b

[

]

[

]

把每个a[][] 看成总面积,把每个b[][]看成小格子的面积

把每个a[][]看成总面积,把每个b[][]看成小格子的面积



    由上图我们可以得到二维差分的定义:在二维情况下,差分就变成了相邻`a[][]`的"面积差’’,计算公式是:  
 





b 


[ 


i 


] 


[ 


j 


] 


= 


a 


[ 


i 


] 


[ 


j 


] 


− 


a 


[ 


i 


− 


1 


] 


[ 


j 


] 


− 


a 


[ 


i 


] 


[ 


j 


− 


1 


] 


+ 


a 


[ 


i 


− 


1 


] 


[ 


j 


− 


1 


] 




\color{Red}b[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1] 


b[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1]  
 




     



即利用上图红色大面积

a

[

i

]

[

j

]

\color{Maroon}a[i][j]

a[i][j]减去两个小面积

a

[

i

1

]

[

j

]

\color{Turquoise}a[i- 1][j]

a[i−1][j]、

a

[

i

]

[

j

]

\color{Green}a[i][j]

a[i][j],由于两个小面积公共的部分a[i-1][j -1]被减去了 2 次,故要加回来 1 次

a

[

i

1

]

[

j

1

]

\color{Yellow}a[i - 1][j - 1]

a[i−1][j−1]。

**(

2

)

\color{Purple}(2) 二维区间修改

(2)二维区间修改**



    对于一维区间修改的操作,我们只需要修改区间的两个端点的`b[]`值。那么相应地,在二维情况下,一块区间是一个矩阵,由4个端点,只需要修改这 4个 `b[][]`值即可。如下图所示,


![在这里插入图片描述](https://img-blog.csdnimg.cn/63656185201942bd84f25af0294c8491.png#pic_center)


当我们对坐标点 `(x1, y1) ~ (x2, y2)`所围成的区间进行修改时,对应的4个端点的操作应为:



```
b[x1][y1] += c; // 二维区间的起点
b[x1][y2 + 1] -= c; // 把 x看成常数,y从 y1 到 y2
b[x2 + 1][y1] -= c;// 把 y看成常熟,x从 x1 到 x2
b[x2 + 1][y2 + 1] += c;// 由于前面两式把 c 减去了 2 次,故要加回 1 次

```

#### 1.2 例题分析


【例题1】[Monitor](https://bbs.csdn.net/topics/618545628)


题意:Xiaoteng 有一个 n×m 的矩形庄稼地,为了抓到小偷,安装了 p 个监控,每个监控都有一个矩形的监视范围,左上角为 (x1,y1),右下角为 (x2,y2)。小偷们会来偷 q 次,每次小偷们的作案地点都是一个矩形区域,左上角为 (x1,y1),右下角为 (x2,y2)。问每次小偷们作案时,能否看到全部的小偷。


思路:将每个监控的矩形监视区域里的每个数都加上 1,都操作在差分数组上。求差分数组的前缀和得到原数组,如果原数组中的值大于 1,说明该点被多个监控覆盖,我们只需要记 1 个即可。对于小偷们每次作案的矩形区域,看监控区域是否全部覆盖(是否全是1),如果全部覆盖(作案矩形同监控矩形的值相等)则输出 YES,否则,输出NO。


**AcCode**



```
#include<bits/stdc++.h>

using namespace std;
typedef long long  LL;

int main()
{
   int n, m;
   
   while(~scanf("%d%d", &n, &m))
   {
       vector<vector<int>> a(n + 10, vector<int>( m + 10, 0));
       
       int k;
       scanf("%d", &k);
       while(k -- )
       {
           int x1, y1, x2, y2;
           scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
           a[x1][y1] += 1;
           a[x2 + 1][y1] -= 1;
           a[x1][y2 + 1] -= 1;
           a[x2 + 1][y2 + 1] += 1;
       }
       
       // 求差分数组的前缀和,得到原数组的值
       for(int i = 1; i <= n; i ++ )
          for(int j = 1; j <= m; j ++ )
              a[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 ++ )
               if(a[i][j] > 1) a[i][j] = 1;
       
       // 对于小偷们每次作案的矩形区域,看监控区域是否全部覆盖(是否全是1)
       int p;
       scanf("%d", &p);
       while(p -- )
       {
           int x1, y1, x2, y2;
           scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
           
           int s1 = (x2 - x1 + 1) \* (y2 - y1 + 1);
           int s2 = f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 - 1][y1 - 1];
           
           if(s1 == s2) puts("YES");
           else puts("NO");
       }
   }
   return 0;
}

```

### 3.三维差分


#### 1.1 基本概念







     



三维已是人类空间想象的一个大跨度,其差分难度较为复杂,不过没关系,下面我们将利用空间立体图来逐一理解。

**(

1

)

\color{Purple}(1)三维差分的定义

(1)三维差分的定义**



    元素值用三维数组 `a[][][]`来定义,差分数组`b[][][]`也是三维的。与之前低维度的差分类似,把三维差分想象成立体空间的操作。与之对应的小立方块有 8 个顶点,所以三维的区间需要修改 8 个`b[][][]`的值。







     



在二维差分中,a[][] 是差分数组 b[][]的前缀和,即原点坐标 (1,1)和 坐标(i,j)围成的矩阵面积。



    在三维差分中,`a[][][]` 是差分数组 `b[][][]`的前缀和,即原点坐标 (1, 1, 1) 和 坐标(i, j, k)围成的立体体积。同样地,我们把每个`b[][][]`看成一个小立方体,在坐标`(1, 1, 1)` ~ `(i , j,k)`所围成的空间中,所有小立体加起来的总体积即为`a[i][j][k]`。如下图所示,每个小立方体由 8 个端点定义。坐标点(i,j,k)的值是 `a[i][j][k]`; 图中小立方体的体积是差分数组 `b[i][j][k]`的值。


![在这里插入图片描述](https://img-blog.csdnimg.cn/4301dd24e1b14d3db4f9a81a5121566a.png#pic_center)







     



类似的,在三维情况下,差分就变成了相邻的a[][][]的 ”体积差“。那么如何来写出差分的递推计算公式呢?

观察前面一、二维的前缀和我们可以发现,其前缀和规律十分吻合容斥原理。

在这里插入图片描述

即对于

t

\color{Red}维度为 t

维度为t 的前缀和,记 S(t) 为其前缀和的递推式,则我们有:

S

(

t

)

=

a

[

t

]

n

=

1

(

1

)

(

n

1

)

S

(

[

t

1

]

)

n

1

S(t) = a[t]+ \sum_{n = 1}{∞}(-1){(n -1)}S( [t- 1]的组合形式),\color{CadetBlue}n~为 -1的个数

S(t)=a[t]+n=1∑∞​(−1)(n−1)S([t−1]的组合形式),n 为−1的个数
所以对于三维的差分数组b[][][],其递推式如下:

b

[

i

]

[

j

]

[

k

]

=

s

[

i

]

[

j

]

[

k

]

s

[

i

1

]

[

j

]

[

k

]

s

[

i

]

[

j

1

]

[

k

]

s

[

i

]

[

j

]

[

k

1

]

s

[

i

1

]

[

j

1

]

[

k

]

s

[

i

1

]

[

j

]

[

k

1

]

s

[

i

]

[

j

1

]

[

k

1

]

s

[

i

1

]

[

j

1

]

[

k

1

]

\color{Red}b[i][j][k] = s[i][j][k]-s[i - 1][j][k] - s[i][j - 1][k] - s[i][j][k - 1] + s[i - 1][j - 1][k] + s[i - 1][j][k - 1] + s[i][j - 1][k - 1] - s[i - 1][j - 1][k - 1]

b[i][j][k]=s[i][j][k]−s[i−1][j][k]−s[i][j−1][k]−s[i][j][k−1]+s[i−1][j−1][k]+s[i−1][j][k−1]+s[i][j−1][k−1]−s[i−1][j−1][k−1]
我们发现当维度为 t 的时候容斥的时间复杂度是

2

t

2^t

2t,而前缀和的总时间复杂度为 **O

(

n

t

2

t

)

O(n t2t)

O(nt2t)**,即随着维度的升高,时间复杂度增大的很快,不过是可以优化到 **O

(

n

t

t

)

O(n^tt)

O(ntt)** 的,但在此不展开讨论,因为在算法竞赛中很少遇到3维以上的前缀和,而对于 t≤3

O

(

n

t

2

t

)

O(n t2t)

O(nt2t) 与

O

(

n

t

t

)

O(n^tt)

O(ntt)差别并不大,有兴趣的可自行查阅资料。

**(

2

)

\color{Purple}(2) 三维区间修改

(2)三维区间修改**



    在三维情况下,我们修改的是一个立方体,有8个顶点,故我们只需要修改这8个顶点的 差分数组`b[][][]`的值即可。给出坐标点 




( 


x 


1 


, 


  


y 


1 


) 



(x1 , ~y1) 


(x1, y1) ~  




( 


x 


2 


, 


  


y 


2 


) 



(x2 ,~y2) 


(x2, y2)定义的区间,如下图所示


![在这里插入图片描述](https://img-blog.csdnimg.cn/a8f0aa301ae54d86b008c4ff4f0055cc.png#pic_center)







三 


维 


差 


分 


空 


间 


图 


示 



三维差分空间图示 


三维差分空间图示  
那么对应的 8个 `b[][][]`的修改如下:



```
// 前面
b[x1][y1][z1] += c; // 坐标起点
b[x2 + 1][y1][z1] -= c; // 右下顶点的右边一个点
b[x1][y1][z2 + 1] -= c; // 左上顶点的上面一个点
b[x2 + 1][y1][z2 + 1] += c; // 右上顶点的斜右上方一个点

// 后面
b[x1][y2 + 1][z1] -= c; // 左下顶点的后面一个点
b[x2 + 1][y2 + 1][z1] += c; // 右下顶点的斜右后方一个点
b[x1][y2 + 1][z2 + 1] += c; // 左上顶点的斜后上方一个点
b[x2 + 1][y2 + 1][z2 + 1] -= c; // 右上顶点的斜右上后方一个点,即区间终点的后一个点

```

可以发现坐标偏移加 1 都与终点相关,为了方便记忆,我们可以把它按照二进制的排列来写,若二进制中出现偶数个 `正1`为正,奇数个`正1`为负,与前缀和恰好相反(前缀和是偶数个 `负1` 为负,奇数个 `负1`为正):



```
{0, 0, 0,  1} // x1, y1, z1
{0, 0, 1, -1}// x1, y1, z2 + 1
{0, 1, 0, -1}// x1, y2 + 1, z1
{0, 1, 1,  1}// x1, y2 + 1, z2 + 1

{1, 0, 0, -1}// x2 + 1, y1, z1
{1, 0, 1,  1}// x2 + 1, y1, z2 + 1
{1, 1, 0,  1}// x2 + 1, y2 + 1, z1
{1, 1, 1, -1}// x2 + 1, y2 + 1, z2 + 1

```

#### 1.2 例题分析


【例题1】[三体攻击](https://bbs.csdn.net/topics/618545628)(蓝桥杯2018年省赛 A组)


【**题目描述**】:







     



三体人将对地球发起攻击。为了抵御攻击,地球人派出了n = A × B × C艘战舰,在太空中排成一个 A 层 B 行 C 列的立方体。其中,第 i 层第 j 行第 k 列的战舰(记为战舰(i, j, k))的生命值为 s(i, j, k)



    三体人将会对地球发起 m 轮“立方体攻击”,每次攻击会对一个小立方体中的所有战舰都造成相同的伤害。具体地,第 t 轮攻击用 7 个参数 `x1, x2, y1, y2, z1, z2, d`描述; 所有满足`i∈[x1, x2], j∈[y1, y2], k∈[z1, z2]`的战舰 `(i, j, k)`会受到 `d` 的伤害。如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。  
 





![img](https://img-blog.csdnimg.cn/img_convert/88ec807ea07b0f88aefa66d6eef5efb0.png)
![img](https://img-blog.csdnimg.cn/img_convert/afb7fdf583f3ef399f792b5d4281fa4d.png)
![img](https://img-blog.csdnimg.cn/img_convert/80cfcbb2e3cc6871f8605955d7919979.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**








三 


维 


差 


分 


空 


间 


图 


示 



三维差分空间图示 


三维差分空间图示  
那么对应的 8个 `b[][][]`的修改如下:



```
// 前面
b[x1][y1][z1] += c; // 坐标起点
b[x2 + 1][y1][z1] -= c; // 右下顶点的右边一个点
b[x1][y1][z2 + 1] -= c; // 左上顶点的上面一个点
b[x2 + 1][y1][z2 + 1] += c; // 右上顶点的斜右上方一个点

// 后面
b[x1][y2 + 1][z1] -= c; // 左下顶点的后面一个点
b[x2 + 1][y2 + 1][z1] += c; // 右下顶点的斜右后方一个点
b[x1][y2 + 1][z2 + 1] += c; // 左上顶点的斜后上方一个点
b[x2 + 1][y2 + 1][z2 + 1] -= c; // 右上顶点的斜右上后方一个点,即区间终点的后一个点

```

可以发现坐标偏移加 1 都与终点相关,为了方便记忆,我们可以把它按照二进制的排列来写,若二进制中出现偶数个 `正1`为正,奇数个`正1`为负,与前缀和恰好相反(前缀和是偶数个 `负1` 为负,奇数个 `负1`为正):



```
{0, 0, 0,  1} // x1, y1, z1
{0, 0, 1, -1}// x1, y1, z2 + 1
{0, 1, 0, -1}// x1, y2 + 1, z1
{0, 1, 1,  1}// x1, y2 + 1, z2 + 1

{1, 0, 0, -1}// x2 + 1, y1, z1
{1, 0, 1,  1}// x2 + 1, y1, z2 + 1
{1, 1, 0,  1}// x2 + 1, y2 + 1, z1
{1, 1, 1, -1}// x2 + 1, y2 + 1, z2 + 1

```

#### 1.2 例题分析


【例题1】[三体攻击](https://bbs.csdn.net/topics/618545628)(蓝桥杯2018年省赛 A组)


【**题目描述**】:







     



三体人将对地球发起攻击。为了抵御攻击,地球人派出了n = A × B × C艘战舰,在太空中排成一个 A 层 B 行 C 列的立方体。其中,第 i 层第 j 行第 k 列的战舰(记为战舰(i, j, k))的生命值为 s(i, j, k)



    三体人将会对地球发起 m 轮“立方体攻击”,每次攻击会对一个小立方体中的所有战舰都造成相同的伤害。具体地,第 t 轮攻击用 7 个参数 `x1, x2, y1, y2, z1, z2, d`描述; 所有满足`i∈[x1, x2], j∈[y1, y2], k∈[z1, z2]`的战舰 `(i, j, k)`会受到 `d` 的伤害。如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。  
 





[外链图片转存中...(img-eW0ycUuu-1715772041056)]
[外链图片转存中...(img-FN2s6bSH-1715772041056)]
[外链图片转存中...(img-UC7LFC9h-1715772041056)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值