三体攻击
三体攻击
三体人将对地球发起攻击。
为了抵御攻击,地球人派出了 $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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/15933472.html

浙公网安备 33010602011771号