三体攻击

三体攻击

三体人将对地球发起攻击。

为了抵御攻击,地球人派出了 $A \times B \times C$ 艘战舰,在太空中排成一个 $A$ 层 $B$ 行 $C$ 列的立方体。

其中,第 $i$ 层第 $j$ 行第 $k$ 列的战舰(记为战舰 $\left( {i,j,k} \right)$)的生命值为 $d \left( {i,j,k} \right)$。

三体人将会对地球发起 $m$ 轮“立方体攻击”,每次攻击会对一个小立方体中的所有战舰都造成相同的伤害。

具体地,第 $t$ 轮攻击用 $7$ 个参数 $la_{t},ra_{t},lb_{t},rb_{t},lc_{t},rc_{t},h_{t}$ 描述;

所有满足 $i \in \left[ {la_{t},ra_{t}} \right],j \in \left[ {lb_{t},rb_{t}} \right],k \in \left[ {lc_{t},rc_{t}} \right]$ 的战舰 $\left( {i,j,k} \right)$ 会受到 $h_{t}$ 的伤害。

如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。

地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。

输入格式

第一行包括 $4$ 个正整数 $A,B,C,m$;

第二行包含 $A \times B \times C$ 个整数,其中第 $\left( {\left( {i−1} \right) \times B+ \left( {j−1} \right)} \right) \times C+ \left( {k−1} \right)+1$ 个数为 $d \left( {i, j, k} \right)$;

第 $3$ 到第 $m+2$ 行中,第 $\left( {t − 2} \right)$ 行包含 $7$ 个正整数 $la_{t},ra_{t},lb_{t},rb_{t},lc_{t},rc_{t},h_{t}$。

输出格式

输出第一个爆炸的战舰是在哪一轮攻击后爆炸的。

保证一定存在这样的战舰。

数据范围

$1 \leq A \times B \times C \leq 10^{6}$,
$1 \leq m \leq 10^{6}$,
$0 \leq d \left( {i, j, k} \right), h_{t} \leq 10^{9}$,
$1 \leq la_{t} \leq ra_{t} \leq A$,
$1 \leq lb_{t} \leq rb_{t} \leq B$,
$1 \leq lc_{t} \leq rc_{t} \leq C$

层、行、列的编号都从 $1$ 开始。

输入样例:

2 2 2 3
1 1 1 1 1 1 1 1
1 2 1 2 1 1 1
1 1 1 2 1 2 1
1 1 1 1 1 1 2

输出样例:

2

样例解释

在第 $2$ 轮攻击后,战舰 $\left( {1,1,1} \right)$ 总共受到了 $2$ 点伤害,超出其防御力导致爆炸。

 

解题思路

  题目大意就是每次攻击都使得某个立方体内的格子都减去一个数,问我们多少轮的攻击后会第一次出现某个格子的数小于$0$。我们会想到用差分,不过这里是三维的差分。同时我们发现,如果某一次攻击后存在某个格子为负数,那么之后的攻击都一定存在为负数的格子,因为每次的攻击都是减去一个非负数。所以答案存在二段性,可以用二分来枚举出答案(我又没想到...二分不一定要在求最值时用到,只要答案满足二段性都可以用二分来枚举答案)。

  比较难的是三维的差分和前缀和。这个其实可以通过二维的情况来找规律推导出来。

  假设原数组为$s \left[ {x,y,z} \right]$,差分数组为$b \left[ {x,y,z} \right]$。原数组可以通过差分数组求前缀和得到。$s \left[ {x,y,z} \right]$求的是,在差分数组$b$中(下标从$1$开始),所有满足$1 \leq i \leq x$,$1 \leq j \leq y$,$1 \leq k \leq z$的点$\left( {i,j,k} \right)$的和。

$$S\left( {x,y,z} \right) = b \left( {x,y,z} \right) + S \left( {x-1,y,z} \right) + S \left( {x,y-1,z} \right) - S \left( {x-1,y-1,z} \right) + S \left( {x,y,z-1} \right) - S \left( {x-1,y,z-1} \right) - S \left( {x,y-1,z-1} \right) + S \left( {x-1,y-1,z-1} \right)$$

  我们结合一维和二维,会发现一个规律,对于$S$(不看$b$),凡是有奇数个坐标减$1$的,$S$对应的系数是正的;凡是有偶数个坐标减$1$的,$S$对应的系数是负的($S\left( {x,y,z} \right)$移到等号右边就系数就变负)。

  这个规律可以推到$n$维的,$S$有$2^{n}$项,并且系数满足上面的规律。

  同时我们通过移项求得$$b \left( {x,y,z} \right) = S\left( {x,y,z} \right) - S \left( {x-1,y,z} \right) - S \left( {x,y-1,z} \right) + S \left( {x-1,y-1,z} \right) - S \left( {x,y,z-1} \right) + S \left( {x-1,y,z-1} \right) + S \left( {x,y-1,z-1} \right) - S \left( {x-1,y-1,z-1} \right)$$

  上面是求三维前缀和的。

  如果我们要对$\left( x_{1},y_{1},z_{1} \right)$,$\left( x_{2},y_{2},z_{2} \right)$这个立方体中的每个格子加上一个数,对$b$的影响一样可以从二维的情况推导:$$\left\{ \begin{matrix} {b\left( {x_{1},y_{1},z_{1}} \right) ~ + = ~val} \\ \begin{matrix} \begin{matrix} {b\left( {x_{1},y_{1},z_{2} + 1} \right) ~ - = ~val} \\ {b\left( {x_{1},y_{2} + 1,z_{2}} \right) ~ - = ~val} \\ {b\left( {x_{1},y_{2} + 1,z_{2} + 1} \right) ~ + = ~val} \\ \end{matrix} \\ {b\left( {x_{2} + 1,y_{1},z_{1}} \right) ~ - = ~val} \\ \end{matrix} \\ \begin{matrix} {b\left( {x_{2} + 1,y_{1},z_{2} + 1} \right) ~ + = ~val} \\ \begin{matrix} {b\left( {x_{2} + 1,y_{2} + 1,z_{1}} \right) ~ + = ~val} \\ {b\left( {x_{2} + 1,y_{2} + 1,z_{2} + 1} \right) ~ - = ~val} \\ \end{matrix} \\ \end{matrix} \\ \end{matrix} \right.$$

  发现一个规律,就是有奇数个坐标加$1$的,对应的$b$要减$val$;有偶数个坐标加$1$的,对应的$b$要加$val$。而且是用$x_{2}$或$y_{2}$或$z_{2}$(即下标编号为$2$)来加$1$。同时下标加$1$的$8$种情况对应二进制$000 \sim 111$(前面求前缀的下标变化也满足)。

  因为这里不知道$A,B,C$的具体值,因此把三维坐标映射到一维。

  我们知道对于大小为$A \times B$的矩阵坐标$\left( {x,y} \right)$映射到一维的下标是$x \cdot B + y$,对于三维坐标$\left( {x,y,z} \right)$,可以把$x,y$看作一维,与$z$组成二维,因此对于大小为$A \times B \times C$(层,行,列)的三维坐标$\left( {x,y,z} \right)$映射到一维的下标是$\left( x \cdot B + y \right) \times C + z$。

  AC代码如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 typedef long long LL;
 7 
 8 const int N = 2e6 + 10; // 因为下标从1开始,所以数组要开大点
 9 
10 int a, b, c;
11 LL s[N], diff[N], backup[N];
12 int op[N >> 1][7];
13 // 下标减偶数个1,系数就为负;下标减奇数个1,系数就为正
14 int d[8][4] = {
15     {0, 0, 0, -1},
16     {0, 0, 1, 1},
17     {0, 1, 0, 1},
18     {0, 1, 1, -1},
19     {1, 0, 0, 1},
20     {1, 0, 1, -1},
21     {1, 1, 0, -1},
22     {1, 1, 1, 1}
23 };
24 
25 int get_idx(int i, int j, int k) {
26     return i * b * c + j * c + k;
27 }
28 
29 bool check(int n) {
30     memcpy(diff, backup, sizeof(backup));
31     for (int i = 0; i < n; i++) {
32         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], t = op[i][6];
33         // 下标的变化满足二进制000~111
34         diff[get_idx(x1, y1, z1)] -= t;
35         diff[get_idx(x1, y1, z2 + 1)] += t;
36         diff[get_idx(x1, y2 + 1, z1)] += t;
37         diff[get_idx(x1, y2 + 1, z2 + 1)] -= t;
38         diff[get_idx(x2 + 1, y1, z1)] += t;
39         diff[get_idx(x2 + 1, y1, z2 + 1)] -= t;
40         diff[get_idx(x2 + 1, y2 + 1, z1)] -= t;
41         diff[get_idx(x2 + 1, y2 + 1, z2 + 1)] += t;
42     }
43     
44     for (int i = 1; i <= a; i++) {
45         for (int j = 1; j <= b; j++) {
46             for (int k = 1; k <= c; k++) {
47                 s[get_idx(i, j, k)] = diff[get_idx(i, j, k)];   // 差分数组求前缀和,根据公式第一个数应该是加diff(i,j,k),这里直接赋值
48                 for (int u = 1; u < 8; u++) {   // 从1开始,根据公式加上对应的7个s
49                     int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
50                     s[get_idx(i, j, k)] += s[get_idx(x, y, z)] * t;
51                 }
52                 
53                 if (s[get_idx(i, j, k)] < 0) return true;   // 发现某个数小于0,返回true
54             }
55         }
56     }
57     
58     return false;
59 }
60 
61 int main() {
62     int m;
63     scanf("%d %d %d %d", &a, &b, &c, &m);
64     for (int i = 1; i <= a; i++) {
65         for (int j = 1; j <= b; j++) {
66             for (int k = 1; k <= c; k++) {
67                 scanf("%lld", &s[get_idx(i, j, k)]);
68                 for (int u = 0; u < 8; u++) {
69                     int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
70                     backup[get_idx(i, j, k)] -= s[get_idx(x, y, z)] * t;    // 根据求b的公式,用s来求
71                 }
72             }
73         }
74     }
75     
76     for (int i = 0; i < m; i++) {
77         for (int j = 0; j < 7; j++) {
78             scanf("%d", &op[i][j]);
79         }
80     }
81     
82     int left = 1, right = m;
83     while (left < right) {
84         int mid = left + right >> 1;
85         if (check(mid)) right = mid;
86         else left = mid + 1;
87     }
88     printf("%d", left);
89     
90     return 0;
91 }

  另外一个版本代码,一开始的不是根据公式计算差分数组diff,而是根据insert函数。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 typedef long long LL;
 7 
 8 const int N = 2e6 + 10;
 9 
10 int a, b, c;
11 LL diff[N], backup[N];
12 int op[N >> 1][7];
13 int d[8][4] = {
14     {0, 0, 0, -1},
15     {0, 0, 1, 1},
16     {0, 1, 0, 1},
17     {0, 1, 1, -1},
18     {1, 0, 0, 1},
19     {1, 0, 1, -1},
20     {1, 1, 0, -1},
21     {1, 1, 1, 1}
22 };
23 
24 int get_idx(int i, int j, int k) {
25     return i * b * c + j * c + k;
26 }
27 
28 void insert(int x1, int x2, int y1, int y2, int z1, int z2, int val) {
29     diff[get_idx(x1, y1, z1)] += val;
30     diff[get_idx(x1, y1, z2 + 1)] -= val;
31     diff[get_idx(x1, y2 + 1, z1)] -= val;
32     diff[get_idx(x1, y2 + 1, z2 + 1)] += val;
33     diff[get_idx(x2 + 1, y1, z1)] -= val;
34     diff[get_idx(x2 + 1, y1, z2 + 1)] += val;
35     diff[get_idx(x2 + 1, y2 + 1, z1)] += val;
36     diff[get_idx(x2 + 1, y2 + 1, z2 + 1)] -= val;
37 }
38 
39 bool check(int n) {
40     memcpy(backup, diff, sizeof(diff));
41     for (int i = 0; i < n; i++) {
42         insert(op[i][0], op[i][1], op[i][2], op[i][3], op[i][4], op[i][5], -op[i][6]);  // 减去一个数,所以传入-op[i][6]
43     }
44     
45     for (int i = 1; i <= a; i++) {
46         for (int j = 1; j <= b; j++) {
47             for (int k = 1; k <= c; k++) {
48                 for (int u = 1; u < 8; u++) {
49                     int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
50                     diff[get_idx(i, j, k)] += diff[get_idx(x, y, z)] * t;
51                 }
52                 
53                 if (diff[get_idx(i, j, k)] < 0) {
54                     memcpy(diff, backup, sizeof(backup));
55                     return true;
56                 }
57             }
58         }
59     }
60     
61     memcpy(diff, backup, sizeof(backup));
62     return false;
63 }
64 
65 int main() {
66     int m;
67     scanf("%d %d %d %d", &a, &b, &c, &m);
68     for (int i = 1; i <= a; i++) {
69         for (int j = 1; j <= b; j++) {
70             for (int k = 1; k <= c; k++) {
71                 int val;
72                 scanf("%d", &val);
73                 insert(i, i, j, j, k, k, val);  // 通过insert函数求初始的差分数组
74             }
75         }
76     }
77     
78     for (int i = 0; i < m; i++) {
79         for (int j = 0; j < 7; j++) {
80             scanf("%d", &op[i][j]);
81         }
82     }
83     
84     int left = 1, right = m;
85     while (left < right) {
86         int mid = left + right >> 1;
87         if (check(mid)) right = mid;
88         else left = mid + 1;
89     }
90     printf("%d", left);
91     
92     return 0;
93 }

 

参考资料

  AcWing 1232. 三体攻击(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/689/

posted @ 2022-02-24 22:34  onlyblues  阅读(166)  评论(0编辑  收藏  举报
Web Analytics