三体攻击
三体攻击
三体人将对地球发起攻击。
为了抵御攻击,地球人派出了 艘战舰,在太空中排成一个 层 行 列的立方体。
其中,第 层第 行第 列的战舰(记为战舰 )的生命值为 。
三体人将会对地球发起 轮“立方体攻击”,每次攻击会对一个小立方体中的所有战舰都造成相同的伤害。
具体地,第 轮攻击用 个参数 描述;
所有满足 的战舰 会受到 的伤害。
如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。
地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。
输入格式
第一行包括 个正整数 ;
第二行包含 个整数,其中第 个数为 ;
第 到第 行中,第 行包含 个正整数 。
输出格式
输出第一个爆炸的战舰是在哪一轮攻击后爆炸的。
保证一定存在这样的战舰。
数据范围
,
,
,
,
,
层、行、列的编号都从 开始。
输入样例:
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
样例解释
在第 轮攻击后,战舰 总共受到了 点伤害,超出其防御力导致爆炸。
解题思路
题目大意就是每次攻击都使得某个立方体内的格子都减去一个数,问我们多少轮的攻击后会第一次出现某个格子的数小于。我们会想到用差分,不过这里是三维的差分。同时我们发现,如果某一次攻击后存在某个格子为负数,那么之后的攻击都一定存在为负数的格子,因为每次的攻击都是减去一个非负数。所以答案存在二段性,可以用二分来枚举出答案(我又没想到...二分不一定要在求最值时用到,只要答案满足二段性都可以用二分来枚举答案)。
比较难的是三维的差分和前缀和。这个其实可以通过二维的情况来找规律推导出来。
假设原数组为,差分数组为。原数组可以通过差分数组求前缀和得到。求的是,在差分数组中(下标从开始),所有满足,,的点的和。
我们结合一维和二维,会发现一个规律,对于(不看),凡是有奇数个坐标减的,对应的系数是正的;凡是有偶数个坐标减的,对应的系数是负的(移到等号右边就系数就变负)。
这个规律可以推到维的,有项,并且系数满足上面的规律。
同时我们通过移项求得
上面是求三维前缀和的。
如果我们要对,这个立方体中的每个格子加上一个数,对的影响一样可以从二维的情况推导:
发现一个规律,就是有奇数个坐标加的,对应的要减;有偶数个坐标加的,对应的要加。而且是用或或(即下标编号为)来加。同时下标加的种情况对应二进制(前面求前缀的下标变化也满足)。
因为这里不知道的具体值,因此把三维坐标映射到一维。
我们知道对于大小为的矩阵坐标映射到一维的下标是,对于三维坐标,可以把看作一维,与组成二维,因此对于大小为(层,行,列)的三维坐标映射到一维的下标是。
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效