【详解】手撕 一维、二维、三维差分数组原理(附图解,模板,例题分析)

news2024/11/30 5:00:30

【差分专题】

引言

​ 差分是一种处理数据巧妙而简单的方法,可以应用于区间修改和询问问题。例如,将给定的数据集合 A 分成很多区间,并对这些区间进行很多次操作,每次都是对某段区间内的所有元素做相同的加减操作,此时若一个个的遍历修改这段区间内的每个元素,就会非常的耗时,效率不高。为此,我们引入一个叫 “差分数组” \color{Orange}“差分数组” 差分数组 B,当修改这段区间时,只需要对这段区间的 端点 \color{Purple}端点 端点 进行修改,就能将整段区间的元素修改,这个操作是非常平滑的,只需要 O(1) 的复杂度。当我们将所有修改操作进行完毕后,再利用差分数组,即可计算出新的数据集合 A。

​ 对于集合 A,它可以是一维的线性数组 a[ ],二维矩阵 a[ ] [ ], 三维立体 a[ ] [ ] [ ] 。与之对应的,差分数组 B[ ] ,B[ ] [ ] ,B[ ] [ ] [ ] 。当然如果想象力足够的话四维也是有可能的。

1. 一维差分

1.1 基本概念

问题描述 \color{Turquoise}问题描述 问题描述:给定一个长度为 n 的一维数组 a[ ],数组内的每个元素都有初值,并对其进行一下操作:

(1)修改操作:进行 m 次区间修改,每次修改对这段区间【L,R】内的所有元素做相同的加减操作。

(2)查询操作:查询某个元素的值是多少

     ~~~~      如果简单的暴力遍历,那么对于每次的修改复杂度是 O(n) 的,有 m 次修改,此时复杂度为 O(n*m) ,如果用差分法的话,复杂度将为 O(n + m)

原理

     ~~~~      对于这个原数组 a[ ] = {a1,a2,a3,···,an},我们构造出这样一个数组 B[ ] = {b1,b2,b3,···,bn},使得 ai = b1 + b2 + ··· + bi,那么b[ ] 就称为 a[ ] 的差分,a[ ] 称为 b[ ] 的前缀和。可以发现,差分与前缀和是一组逆运算。

     ~~~~     如何利用差分数组对区间进行修改呢?为什么利用差分数组能提升修改的效率呢?

1. 区间修改 \color{Purple}区间修改 区间修改,时间复杂度为 O(1)

​ 现在要将原数组 a[ ] 区间【L,R】上的每个数都加上一个 c,如下图所示:

在这里插入图片描述

  • 第一个受到影响的差分数组中的元素为 bL],所以 b[L] += c ,对于 a[L] 后面的数都会受到 B[L] 的影响加上 c。
  • 最后一个受影响的差分数组中的元素b[R],为了保证不影响到 R 后面的元素,所以我们需要 b[R + 1] -= c

也就是说,对于 a[x] = b[1] + b[2] + ···+ b[x],利用差分数组能够精确地实现只修改指定区间内元素的目的,而不会修改区间外的a[ ] 的值,也就是:

  • (1) 1 ≤ x < L, 前缀和 a[x] 不变。
  • (2) L ≤ x ≤ R, 前缀和 a[x] 加上了 c 。
  • (3) R < x ≤ N, 前缀和 a[x] 不变,因为被 b[R + 1] 中的c抵消了。

这样一来,就不必每次都对区间内所有的数进行处理,只需要修改区间【L,R】的两个端点 b[ ] 的值即可,复杂度是 O(1) 的。

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

2. 单点查询 \color{Purple}单点查询 单点查询,时间复杂度为 O(n)

     ~~~~      根据定义,差分数组 b[ x ] 的前缀和 b[1] + b[ 2 ] + ··· + b[ x ] 就是原数组 a[x] 的值

void get(int x)
{
    for(int i = 1; i <= n; i ++ )
        sum[i] = sum[i - 1] + b[i];
    
    cout << sum[x];
}

初始化:我们其实不需要过分关注差分数组 b[] 是如何构造出来的,只需要知道差分与前缀和是互为逆运算即可。实际上,我们压根就不需要去构造差分数组 b[]

     ~~~~      一开始,我们就可以把原数组 a[] 看成全是 0,即 a[ ] = {0,0,0,···0} ,此时相应的差分数组 b[] 也全是 0,即 b[ ] = {0,0,0,···0},那么对于原数组 a[] 的初始值:

  • a[ 1 ] 相当于区间 【1,1】的每个数都加上 a[ 1 ]
  • a[ 2 ] 相当于区间 【2,2】的每个数都加上 a[ 2 ]
  • a[ n ] 相当于区间 【n,n】的每个数都加上 a[ n ]

这样,只需要用上面的区间修改操作 add() 即可完成初始化赋值了。

1.2 例题分析

【例题1】 区间(interval)

题目描述:小明喜欢数组。他现在有一个 n 个元素的数组 a,而他要对 a[L] ~ a[R]进行M次操作:

操作一:将 a[L] ~ a[R]内的元素都加上 c

操作二:将 a[L] ~ a[R]内的元素都减去 c

最后询问 a[L] ~ a[R] 内的元素之和

AcCode

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1000010;

LL a[N];

void add(int l, int r, int c)
{
    a[l] += c;
    a[r + 1] -= c;
}
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    
    for(int i = 1; i <= n; i ++ ) // 将数组 a[] 看成自己的差分
    {
        int x;
        scanf("%d", &x);
        add(i, i, x);
    }
    
    for(int i = 1; i <= m; i ++ )// 修改操作
    {
        int k, l, r, c;
        scanf("%d%d%d%d", &k, &l, &r, &c);
        if(k == 1) add(l, r, -c);
        else add(l, r, c);
    }
    
    for(int i = 1; i <= n; i ++ )// 求差分数组 a[]的前缀和(即修改后a[] 的值)
       a[i] += a[i - 1];
      
    int l, r;
    scanf("%d%d", &l, &r);
    
    LL ans = 0;
    for(int i = l; i <= r; i ++ )
            ans += a[i];
    
    printf("%lld\n", ans);
    
    return 0;
    
}

差分的局限性:我们可以注意到,利用差分数组 b[] 可以将原来 O ( n ) O(n) O(n) 的区间修改,降为 O ( 1 ) O(1) O(1)的端点修改,从而提高了修改操作的效率。

     ~~~~     但是,对于一次的查询操作,我们必须计算前缀和 b[1] + b[2] + ··· + b[x]才能将原数组 a[x] 求出,计算量是 O ( n ) O(n) O(n)的,即一次查询的复杂度是 O ( n ) O(n) O(n)的。也就是说,如果查询操作发生多次,例如 m 次修改,k 次查询,且修改和查询的顺序是随机的,即可能边修改边查询。此时总复杂度为:m 次修改复杂度 O ( m ) O(m) O(m),k次查询复杂度 O ( k n ) O(kn) O(kn),即 o ( m + k n ) o(m + kn) o(m+kn)。还不如直接暴力来的快 O ( m n + k ) O(mn + k) O(mn+k)

     ~~~~     可以看出,尽管差分数组对于 ”区间修改“很高效,但是对于”单点查询“来说略显吃力。此时有专门的数据结构来解决这一类问题:树状数组和线段树,详见本博客的树状数组和线段树专题。

2. 二维差分

1.1 基本概念

     ~~~~     有了一维差分的认识后,我们容易就能拓展到二维差分。一维是线性的,一段区间【L,R】有两个端点;二维是一个矩阵,一块区间由四个端点所围成。

问题描述 \color{Turquoise}问题描述 问题描述: 在 n × n 的格子上有 m 个地毯。给出这些地毯的信息,问每个点被多少地毯覆盖。

输入: 第一行是两个整数n, m。接下来 m 行,每行 2 个坐标(x1, y1) 和 (x2, y2 ),代表一块地毯,左上角是 (x1, y1),右下角是(x2, y2)。

输出:输出n行,每行n个正整数。第i行第j列的正整数表示(i, j)这个格式被多少地毯覆盖。

     ~~~~     可以发现,这是前面例题区间(interval)的二维拓展,其修改和查询操作完全一样。

     ~~~~     我们知道存储矩阵往往需要很大的空间。如果题目有空间的限制,例如100M = 100 * 1024 *1024 个字节(byte),那么对于矩阵每个元素是 4 个字节的 int型 来说,可以计算出最大的 maxn = 5120。不过,也可以像前面例题一样,不定义差分矩阵 b[][],直接将原矩阵a[][]看成自己的差分矩阵,这样一来就能剩下一半的空间了。

     ~~~~     同前面一样,我们先考虑能不能直接暴力求解。可以看出,每次矩阵修改的复杂度是 O ( n 2 ) O(n^2) O(n2),共 m 次,总复杂度为 O ( m + n 2 ) 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) (i1,j) ( i , j − 1 ) \color{CadetBlue}(i, j - 1) (i,j1) ( i − 1 , j − 1 ) \color{CadetBlue}(i - 1, j - 1) (i1,j1)。坐标点(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[i1][j]a[i][j1]+a[i1][j1]
     ~~~~     即利用上图红色大面积 a [ i ] [ j ] \color{Maroon}a[i][j] a[i][j]减去两个小面积 a [ i − 1 ] [ j ] \color{Turquoise}a[i- 1][j] a[i1][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[i1][j1]

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

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

在这里插入图片描述

当我们对坐标点 (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

题意: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]的值。

在这里插入图片描述

     ~~~~     类似的,在三维情况下,差分就变成了相邻的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)(n1)S([t1]的组合形式)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 ] \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] b[i][j][k]=s[i][j][k]s[i1][j][k]s[i][j1][k]s[i][j][k1]+s[i1][j1][k]+s[i1][j][k1]+s[i][j1][k1]
我们发现当维度为 t 的时候容斥的时间复杂度是 2 t 2^t 2t,而前缀和的总时间复杂度为 O ( n t 2 t ) O(n ^t2^t) O(nt2t),即随着维度的升高,时间复杂度增大的很快,不过是可以优化到 O ( n t t ) O(n^tt) O(ntt) 的,但在此不展开讨论,因为在算法竞赛中很少遇到3维以上的前缀和,而对于 t≤3 O ( n t 2 t ) O(n ^t2^t) 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)定义的区间,如下图所示

在这里插入图片描述

三维差分空间图示 三维差分空间图示 三维差分空间图示
那么对应的 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】三体攻击(蓝桥杯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 的伤害。如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。
     ~~~~     地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。

输入】 :

     ~~~~     第一行包括 4 个正整数 A, B, C, m
     ~~~~     第二行包含A × B × C个整数,其中第 ((i − 1)×B + (j − 1)) × C + (k − 1)+1 个数为 s(i, j, k)
     ~~~~     第 3 到第 m + 2 行中,第 (t − 2) 行包含 7 个正整数 x1, x2, y1, y2, z1, z2, d A × B × C   ≤   1 0 6 ,   m   ≤   1 0 6 ,   0   ≤   s ( i ,   j ,   k ) ,   d   ≤   1 0 9 A × B × C ≤ 10^6, m ≤ 10^6, 0 ≤ s(i, j, k), d ≤ 10^9 A×B×C 106,m 106, 0 s(i,j,k),d 109

输出】:

     ~~~~     输出第一个爆炸的战舰是在哪一轮攻击后爆炸的。保证一定存在这样的战舰。

分析:如果对于每次攻击我们都枚举每个点生命值,对于 n = 1 0 6 n=10^6 n=106 个点, m = 1 0 6 m = 10 ^6 m=106次攻击来说,时间复杂度为 O(nm),肯定会TLE。

     ~~~~     故此时我们需要在差分数组查询空间内每个元素是否小于0,但是计算空间上的每个元素的值是 O(n) 的,对于m次攻击,复杂度仍然是 O(nm)。可见单纯使用差分数组是不行的。为此我们需要利用二分法:题目保证一定存在这样一个战舰在某一轮攻击后会爆炸,即从 第 1 次到 m 次修改,必定有一次是临界值。在临界值以前,战舰的生命肯定是大于 0 的,在临界值后,若战舰出现了负值,则之后一直都会是负值。那么对 m 进行二分,就能在 O(logm) 次内找到这个临界值,这就是答案。总复杂度为 O(nlogm)。

    ~~~    细节处理:如果对于数组空间我们每一维度都开到 1 0 6 10^6 106,必定会MLE。为了避免这一问题,我们将三维数组拍平为一维数组,即采用压维的方式,对于 A × B × C = 1 0 6 10^6 106,数组长度仍然是 1 0 6 10^6 106的。对于 二维数组 【A】【B】,若知道某一元素的下标(i, j),则将它映射为一维数组的下标应为 i× B + j。同样对于三维数组 【A】【B】【C】,先把前两维看成一维的,再把第三维度看成二维,则三维坐标 (i,j,k)对应的映射坐标为(i× B + j) × C + k,实现函数为get()

AcCode

#include<bits/stdc++.h>

using namespace std;
typedef long long  LL;
const int N = 2000010;

int A, B, C, m;// 层,行,列, 攻击轮数
LL s[N], b[N], bp[N];// 生命值, 差分数组,备份差分数组
int op[N / 2][7];// 攻击范围及伤害

// 差分数组八个方向偏移量
int d[8][4] = 
{
    {0, 0, 0,  1},
    {0, 0, 1, -1},
    {0, 1, 0, -1},
    {0, 1, 1,  1},
    {1, 0, 0, -1},
    {1, 0, 1,  1},
    {1, 1, 0,  1},
    {1, 1, 1, -1},
};

int get(int i, int j, int k)// 压维映射函数
{
    return (i * B + j) * C + k;
}
bool check(int mid)
{
    memcpy(b, bp, sizeof b);
    for(int i = 1; i <= mid; i ++ )
    {
        int x1 = op[i][0], x2 = op[i][1], y1 = op[i][2], y2  = op[i][3], z1 = op[i][4], z2 = op[i][5], h = op[i][6];
        b[get(x1,     y1,         z1)] += -h; // 伤害为 负
        b[get(x1,     y1,     z2 + 1)] -= -h;
        b[get(x1,     y2 + 1,     z1)] -= -h;
        b[get(x1,     y2 + 1, z2 + 1)] += -h;
        b[get(x2 + 1, y1,         z1)] -= -h;
        b[get(x2 + 1, y1,     z2 + 1)] += -h;
        b[get(x2 + 1, y2 + 1,     z1)] += -h;
        b[get(x2 + 1, y2 + 1, z2 + 1)] -= -h;
        
    }
   
    /* 求前缀和

     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]+b[i][j][k]

     */
    memset(s, 0, sizeof s);
    for(int i = 1; i <= A; i ++ )
        for(int j = 1; j <= B; j ++ )
            for(int k = 1; k <= C; k ++ )
            {
                s[get(i, j ,k)] = b[get(i, j, k)];
                for(int u = 1; u < 8; u ++ )
                {
                    int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                    s[get(i, j, k)] -= s[get(x, y, z)] * t;

                }
                if(s[get(i, j, k)] < 0) return true;
            }

    return false;

}
int main()
{

    scanf("%d%d%d%d", &A, &B, &C, &m);

    // 生命值读入
    for(int i = 1; i <= A; i ++ )
        for(int j = 1; j <= B; j ++ )
            for(int k = 1; k <= C; k ++ )
                scanf("%lld", &s[get(i, j, k)]);

    // 求差分数组 b[]
    for(int i = 1; i <= A; i ++ )
        for(int j = 1; j <= B; j ++ )
            for(int k = 1; k <= C; k ++)
                for(int u = 0; u < 8; u ++ )
                {
                    int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                    bp[get(i, j, k)] += s[get(x, y, z)] * t;


                }

    // 读入覆盖范围,攻击伤害 
    for(int i = 1; i <= m; i ++ )
        for(int j = 0; j < 7; j ++ )
            scanf("%d", &op[i][j]);
        
    // 二分
    int l = 1, r = m;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d\n", r);


    return 0;
    
}

以上内容尚未完全,随着今后学习的推进,我会继续对其进行补充与完善。另外,大家如果觉得我写的还行的话,还请赠予我一个可爱的赞,你的赞对于我是莫大的支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3155.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv7学习笔记(一)——概述+环境+训练

一、环境安装测试 1、创建环境conda create -n yolo python3.7conda activate yolo2、安装pytorchconda install pytorch torchvision cudatoolkit11.3 -c pytorch11.3为cuda版本号3、克隆yolov5git clone https://github.com/WongKinYiu/yolov7 # clonecd yolov7pip install…

真的要转到云IDE了吗?VS Code的开源IDE

云IDE产品介绍 云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间啦~ 前言 CSDN最新产品【云IDE】来啦&#xff01;【云IDE】将为各位技术er一键秒级构建云开发环境&#xff0c;提升开发效率&#xff01; 1. 什么是IDE&#xff1f; 做…

PyTorch 1.13 正式发布:CUDA 升级、集成多个库、M1 芯片支持

内容导读&#xff1a;近日&#xff0c;PyTorch 团队在官方博客宣布 Pytorch 1.13 发布。本文将详细围绕新版本的 4 大亮点展开介绍。 据官方介绍&#xff0c;PyTorch 1.13 中包括了 BetterTransformer 的稳定版&#xff0c;且不再支持 CUDA 10.2 及 11.3&#xff0c;并完成了向…

大数据项目之电商数仓、业务数据介绍、业务数据模拟、生成业务数据、业务数据建模

文章目录6. 业务数据介绍6.5 业务数据模拟6.5.1 连接MySQL6.5.2 建表语句6.5.3 生成业务数据6.5.3.1 在hadoop102的/opt/module/目录下创建db_log文件夹6.5.3.2 把gmall2020-mock-db-2021-11-14.jar和application.properties上传到hadoop102的/opt/module/db_log路径上6.5.3.3 …

[CSS]常见布局技巧

前言 系列文章目录&#xff1a; [目录]HTML CSS JS 根据视频和PPT整理视频及对应资料&#xff1a;HTML CSS 老师笔记&#xff1a; https://gitee.com/xiaoqiang001/html_css_material.git视频&#xff1a;黑马程序员pink老师前端入门教程&#xff0c;零基础必看的h5(html5)css3…

榛子树搜索算法(Hazelnut tree search algorithm,HTS)附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

c++基础(十四)——继承

一、继承的基本语法 继承是面向对象三大特性之一&#xff0c;有些类与类之间存在特殊的关系&#xff0c;当定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。这个时候我们就可以考虑利用继承的技术&#xff0c;减少重复代码。 基本语法&…

HotStuff:基于高效 BFT-SMR 共识的区块链

参考资料&#xff1a; Yin M, Malkhi D, Reiter M K, et al. HotStuff: BFT consensus in the lens of blockchain[J]. arXiv preprint arXiv:1803.05069, 2018.Michael J. Fischer, Nancy A. Lynch, and Mike Paterson. Impossibility of distributed consensus with one fau…

为什么网络安全缺口很大,而招聘却很少?

2020年我国网络空间安全人才数量缺口超过了140万&#xff0c;就业人数却只有10多万&#xff0c;缺口高达了93%。这里就有人会问了&#xff1a; 1、网络安全行业为什么这么缺人&#xff1f; 2、明明人才那么稀缺&#xff0c;为什么招聘时招安全的人员却没有那么多呢&#xff1…

Spring-security流程讲解01:basic模式

1&#xff1a;有basic模式认证&#xff1a; 1.0Maven依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version> </parent…

UGUI DrawCall的优化 工作记录

优化前&#xff1a; UGUI DrawCall调用256次 优化原则&#xff1a; 不追求极致&#xff0c;差不多就行了主要优化 静态不动的节点&#xff0c;避免改变脚本 优化思路&#xff1a; 3. 对现有的图集进行微调 4. 处理图文混排 打断合批的问题&#xff0c;微调节点的父子关系和节…

图解LeetCode——754. 到达终点数字(难度:中等)

一、题目 在一根无限长的数轴上&#xff0c;你站在 0 的位置。终点在 target 的位置。 你可以做一些数量的移动 numMoves : 每次你可以选择向左或向右移动。第 i 次移动&#xff08;从 i 1 开始&#xff0c;到 i numMoves &#xff09;&#xff0c;在选择的方向上走 i 步。…

vue11Vuex解说+子父传参详细使用

目录 1. vue组件间通讯 1.1 父子组件 1.2 消息总线 1.3 vuex 2. vuex使用 2.1 简介 2.2 安装 2.3 创建store模块 2.4 创建vuex的store实例并注册上面引入的各大模块 2.5 在main.js中导入vuex 3. 将折叠和展开效果使用vuex实现 3.1 在state.js中声明全局参数 3.2 设…

10 月更新 | Visual Studio Code Python

我们很高兴地宣布&#xff0c;2022 年 10 月发布的适用于 Visual Studio Code Python 和 Jupyter 扩展现已推出&#xff01; 此版本包括以下改进&#xff1a; 使用新的创建环境命令简化项目设置 现在可以为 Pylance 提供包含、排除和忽略路径设置 预发布的 autopep8 扩展 P…

【Linux】yum和vim的使用

文章目录一、Linux软件包管理器-yum1. 概念2. 安装软件3. 卸载软件4. 查看软件包5. 更新软件包二、Linux编辑器-vim1. 基本概念2. 基本操作3. 正常模式命令集4. 末行模式命令集三、总结一、Linux软件包管理器-yum 1. 概念 什么是软件包&#xff1f; 在Linux下安装软件, 一个常…

es实操笔记

Elasticsearch提供了一个非常全面和强大的REST API&#xff0c;可以使用它与集群进行交互。咱们来玩一下。 环境搭建 搭建好es和kinaba&#xff0c;可以访问通过ip:9200查看es的信息&#xff0c;ip:5601打开kinaba。 集群相关 健康检查 curl localhost:9200/_cat/health?v…

Linux 网络之netlink 简介

文章目录前言一、Netlink用户态应用的使用1.1 Netlink socket1.2 Netlink宏操作二、Netlink对应数据结构2.1 struct sockaddr_nl2.2 struct nlmsghdr2.3 struct msghdr三、用户层实例参考资料前言 The Netlink socket family 是一个 Linux 内核接口&#xff0c;用于内核和用户…

网课查题接口

网课查题接口 本平台优点&#xff1a;免费查题接口搭建 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a;题库后台http://daili.jueguangzhe.cn/ 题库后台&am…

[前端基础] CSS3 篇

CSS3 使用了层叠样式表技术&#xff0c;可以对网页布局、字体、颜色、背景灯效果做出控制。CSS3 作为 CSS 的进阶版&#xff0c;拆分和增加了盒子模型、列表模块、语言模块 、背景边框 、文字特效 、多栏布局等等。CSS3 的改变有很多&#xff0c;增加了文字特效&#xff0c;丰富…

C#基础知识

文章目录简介隐式显式转换ifswitch循环随机数方法do whilestatic递归数组foreachvar推断类型,父类二维数组&#xff0c;交错数组当方法参数类型确定&#xff0c;个数不确定时数据类型类型分类快捷方式函数参数&#xff1a;引用、输出参数装箱&#xff0c;拆箱string储存地址枚举…