三体攻击

三体攻击

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

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

其中,第 i 层第 j 行第 k 列的战舰(记为战舰 (i,j,k))的生命值为 d(i,j,k)

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

具体地,第 t 轮攻击用 7 个参数 lat,rat,lbt,rbt,lct,rct,ht 描述;

所有满足 i[lat,rat],j[lbt,rbt],k[lct,rct] 的战舰 (i,j,k) 会受到 ht 的伤害。

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

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

输入格式

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

第二行包含 A×B×C 个整数,其中第 ((i1)×B+(j1))×C+(k1)+1 个数为 d(i,j,k)

3 到第 m+2 行中,第 (t2) 行包含 7 个正整数 lat,rat,lbt,rbt,lct,rct,ht

输出格式

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

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

数据范围

1A×B×C106,
1m106,
0d(i,j,k),ht109,
1latratA,
1lbtrbtB,
1lctrctC

层、行、列的编号都从 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 轮攻击后,战舰 (1,1,1) 总共受到了 2 点伤害,超出其防御力导致爆炸。

 

解题思路

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

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

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

S(x,y,z)=b(x,y,z)+S(x1,y,z)+S(x,y1,z)S(x1,y1,z)+S(x,y,z1)S(x1,y,z1)S(x,y1,z1)+S(x1,y1,z1)

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

  这个规律可以推到n维的,S2n项,并且系数满足上面的规律。

  同时我们通过移项求得b(x,y,z)=S(x,y,z)S(x1,y,z)S(x,y1,z)+S(x1,y1,z)S(x,y,z1)+S(x1,y,z1)+S(x,y1,z1)S(x1,y1,z1)

  上面是求三维前缀和的。

  如果我们要对(x1,y1,z1)(x2,y2,z2)这个立方体中的每个格子加上一个数,对b的影响一样可以从二维的情况推导:{b(x1,y1,z1) += valb(x1,y1,z2+1) = valb(x1,y2+1,z2) = valb(x1,y2+1,z2+1) += valb(x2+1,y1,z1) = valb(x2+1,y1,z2+1) += valb(x2+1,y2+1,z1) += valb(x2+1,y2+1,z2+1) = val

  发现一个规律,就是有奇数个坐标加1的,对应的b要减val;有偶数个坐标加1的,对应的b要加val。而且是用x2y2z2(即下标编号为2)来加1。同时下标加18种情况对应二进制000111(前面求前缀的下标变化也满足)。

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

  我们知道对于大小为A×B的矩阵坐标(x,y)映射到一维的下标是xB+y,对于三维坐标(x,y,z),可以把x,y看作一维,与z组成二维,因此对于大小为A×B×C(层,行,列)的三维坐标(x,y,z)映射到一维的下标是(xB+y)×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 @   onlyblues  阅读(181)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示