三体攻击(不知道为什么练习系统会超时,但是在这里提交就可以)
三体攻击
三体人将对地球发起攻击。为了抵御攻击,地球人派出 A × B × C A \times B \times C A × B × C 艘战舰,在太空中排成一个 A 层 B 行 C 列的立方体。其中,第 i 层第 j 行第 k 列的战舰(记为战舰 ( i , j , k ) (i, j, k) (i, j, k))的生命值为 d ( i , j , k ) d(i, j, k) d(i, j, k)。
三体人将会对地球发起 m 轮"立方体攻击",每次攻击会对一个小立方体中的所有战舰都造成相同的伤害。具体地,第 t 轮攻击用 7 个参数 l a t , r a t , l b t , r b t , l c t , r c t , h t lat, rat, lbt, rbt, lct, rct, ht lat, rat, lbt, rbt, lct, rct, ht 描述;
所有满足 i ∈ [ l a t , r a t ] , j ∈ [ l b t , r b t ] , k ∈ [ l c t , r c t ] i ∈ [lat, rat],j ∈ [lbt, rbt],k ∈ [lct, rct] i ∈ [lat, rat],j ∈ [lbt, rbt],k ∈ [lct, rct] 的战舰 ( i , j , k ) (i, j, k) (i, j, k) 会受到 ht 的伤害。如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。
地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。
输入描述
输入格式:
第一行包括 4 个正整数 A, B, C, m;
第二行包含 A × B × C A \times B \times C A × B × C 个整数,其中第 ( ( i − 1 ) × B + ( j − 1 ) ) × C + ( k − 1 ) + 1 ((i − 1)\times B + (j − 1)) \times C + (k − 1)+1 ((i − 1)×B + (j − 1)) × C + (k − 1)+1 个数为 d ( i , j , k ) d(i, j, k) d(i, j, k);
第 3 到第 m + 2 行中,第 (t − 2) 行包含 7 个正整数 l a t , r a t , l b t , r b t , l c t , r c t , h t lat, rat, lbt, rbt, lct, rct, ht lat, rat, lbt, rbt, lct, rct, ht。
其中, A × B × C ≤ 1 0 6 , m ≤ 1 0 6 , 0 ≤ d ( i , j , k ) , h t ≤ 1 0 9 A \times B \times C \leq 10^6, m \leq 10^6, 0 \leq d(i, j, k), ht \leq 10^9 A × B × C ≤ 106, m ≤ 106, 0 ≤ d(i, j, k), ht ≤ 109。
输出描述
输出第一个爆炸的战舰是在哪一轮攻击后爆炸的。保证一定存在这样的战舰。
输入输出样例
输入
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
思路
- 暴力+二分+三维差分优化
- 由于是求第几轮第一艘战舰爆炸,可知,在第一艘战舰爆炸之前没有战舰爆炸,之后肯定有战舰爆炸,符合“单调性”,可以用二分
- 整体框架是二分,关键就在于二分的check()函数怎么写了,若是单纯地暴力,那么会超时,这里使用差分数组
- 关于差分数组的知识这里省略,其实可以手动模拟出来,只是有些费时,
三维的还会看花眼 - 得出三维的差分数组求法:
//三维差分数组求法
//a是原数组,是p层n行m列的三维数组
for (int i = 1; i <= p; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= m; k++) {
b[i][j][k] = a[i][j][k] - a[i - 1][j][k] - a[i][j][k - 1] +
a[i - 1][j][k - 1] - a[i][j - 1][k] +
a[i - 1][j - 1][k] + a[i][j - 1][k - 1] -
a[i - 1][j - 1][k - 1];
}
}
}
//给点(x1,y1,z1)到点(x2,y2,z2)之间的三维区间都加上d
//b是差分数组
void change_matrix(int x1, int y1, int z1, int x2, int y2, int z2, int d) {
b[z1][x1][y1] += d;
b[z1][x1][y2 + 1] -= d;
b[z1][x2 + 1][y1] -= d;
b[z1][x2 + 1][y2 + 1] += d;
b[z2 + 1][x1][y2 + 1] += d;
b[z2 + 1][x2 + 1][y1] += d;
b[z2 + 1][x1][y1] -= d;
b[z2 + 1][x2 + 1][y2 + 1] -= d;
}
- 不过以上都是三维形式的数组,这道题还要坑一些,用三维数组有点麻烦,要使用动态开辟空间,这里就将它转为一维数组,转换方法题目已经给了,写成一个函数即可
int id(int i, int j, int k) {
if (i > A || j > B || k > C) return 0;
return ((i - 1) * B + (j - 1)) * C + (k - 1) + 1;
}
- 有了这些之后,就可以写出check()函数了,check()函数的思路是:对于某一轮攻击 t t t,如果从第一轮攻击到第 t t t 轮攻击使得有战舰爆炸,那么就返回真,否则返回假
代码如下
#include <cstdio>
#include <vector>
using namespace std;
vector<int> d; //存原始数据
vector<vector<int> > dk; //存攻击
int A, B, C, n, m; // A层B行C列m轮,n = A*B*C
//下标转换
int id(int i, int j, int k) {
if (i > A || j > B || k > C) return 0;
return ((i - 1) * B + (j - 1)) * C + (k - 1) + 1;
}
// check()函数
bool check(int t) {
int i = 0, j = 0, k = 0;
vector<int> a;
a.resize(n + 1, 0);
for (i = 1; i <= t; i++) {
//全部转换为一维下标
a[id(dk[i - 1][0], dk[i - 1][2], dk[i - 1][4])] += dk[i - 1][6];
a[id(dk[i - 1][1] + 1, dk[i - 1][2], dk[i - 1][4])] -= dk[i - 1][6];
a[id(dk[i - 1][0], dk[i - 1][3] + 1, dk[i - 1][4])] -= dk[i - 1][6];
a[id(dk[i - 1][0], dk[i - 1][2], dk[i - 1][5] + 1)] -= dk[i - 1][6];
a[id(dk[i - 1][1] + 1, dk[i - 1][3] + 1, dk[i - 1][4])] += dk[i - 1][6];
a[id(dk[i - 1][0], dk[i - 1][3] + 1, dk[i - 1][5] + 1)] += dk[i - 1][6];
a[id(dk[i - 1][1] + 1, dk[i - 1][2], dk[i - 1][5] + 1)] += dk[i - 1][6];
a[id(dk[i - 1][1] + 1, dk[i - 1][3] + 1, dk[i - 1][5] + 1)] -=
dk[i - 1][6];
}
//这三个三重循环是求前缀和
for (i = 1; i <= A; i++) {
for (j = 1; j <= B; j++) {
for (k = 1; k < C; k++) {
a[id(i, j, k + 1)] += a[id(i, j, k)];
}
}
}
for (i = 1; i <= A; i++) {
for (k = 1; k <= C; k++) {
for (j = 1; j < B; j++) {
a[id(i, j + 1, k)] += a[id(i, j, k)];
}
}
}
for (j = 1; j <= B; j++) {
for (k = 1; k <= C; k++) {
for (i = 1; i < A; i++) {
a[id(i + 1, j, k)] += a[id(i, j, k)];
}
}
}
for (i = 1; i <= n; i++) {
if (a[i] > d[i - 1]) {
return true;
}
}
return false;
}
//读入数据
void init() {
scanf("%d%d%d%d", &A, &B, &C, &m);
n = A * B * C;
int i = 0, j = 0;
d.resize(n);
for (i = 0; i < n; i++) {
scanf("%d", &d[i]);
}
dk.resize(m);
for (i = 0; i < m; i++) {
dk[i].resize(7);
for (j = 0; j < 7; j++) {
scanf("%d", &dk[i][j]);
}
}
}
//二分
void solve() {
int l = 1, r = m, mid = 0, ans = 0;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
printf("%d\n", ans);
}
int main(void) {
init();
solve();
return 0;
}