三维差分—三体攻击
Description
三体人将对地球发起攻击。
为了抵御攻击,地球人派出了 A×B×C艘战舰,在太空中排成一个 A 层 B 行 C列的立方体。
其中,第 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 的伤害。
如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。
地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。
Input
第一行包括 4个正整数 A,B,C,m;第二行包含 A×B×C个整数,其中第 ((i−1)×B+(j−1))×C+(k−1)+1个数为 d(i, j, k);第 3 到第 m+2 行中,第 (t − 2) 行包含 7 个正整数 lat, rat, lbt, rbt, lct, rct, ht。
Output
输出第一个爆炸的战舰是在哪一轮攻击后爆炸的。
保证一定存在这样的战舰。
Sample Input
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
Sample Output
2
More Info
1≤A×B×C≤106,
1≤m≤106,
0≤d(i, j, k), ht≤109,
1≤lat≤rat≤A,
1≤lbt≤rbt≤B,
1≤lct≤rct≤C
层、行、列的编号都从 1 开始。
分析
这道题从空间为出发点,所以直接用三维差分来记录操作,但是题目要求知道每轮攻击操作后战舰剩余血量,这就要每次进行查找操作,总的复杂度就是O(mn)和暴力法是一样的,所以就需要优化查找次数,可以看到攻击次数越多血量越少是单调的,所以可以采用二分法。优化后总的复杂度就是O(nlog2m)
代码实现
#include<stdio.h>
int A,B,C,n,m;
const int N = 1000005;
int s[N]; //存储舰队生命值
int D[N]; //三维差分数组(压维,节省空间);同时也用来计算每个点的攻击值
int x2[N], y2[N], z2[N]; //存储区间修改的范围,即攻击的范围
int x1[N], y1[N], z1[N];
int d[N]; //记录伤害,就是区间修改
int num(int x,int y,int z) {
//小技巧:压维,把三维坐标[(x,y,z)转为一维的((x-1)*B+(y-1))*C+(z-1)+1
if (x>A || y>B || z>C) return 0;
return ((x-1)*B+(y-1))*C+(z-1)+1;
}
bool check(int x){ //做x次区间修改。即检查经过x次攻击后是否有战舰爆炸
for (int i=1; i<=n; i++) D[i]=0; //差分数组的初值,本题是0
for (int i=1; i<=x; i++) { //用三维差分数组记录区间修改:有8个区间端点
D[num(x1[i], y1[i], z1[i])] += d[i];
D[num(x2[i]+1,y1[i], z1[i])] -= d[i];
D[num(x1[i], y1[i], z2[i]+1)] -= d[i];
D[num(x2[i]+1,y1[i], z2[i]+1)] += d[i];
D[num(x1[i], y2[i]+1,z1[i])] -= d[i];
D[num(x2[i]+1,y2[i]+1,z1[i])] += d[i];
D[num(x1[i], y2[i]+1,z2[i]+1)] += d[i];
D[num(x2[i]+1,y2[i]+1,z2[i]+1)] -= d[i];
}
//下面从x、y、z三个方向计算前缀和
for (int i=1; i<=A; i++)
for (int j=1; j<=B; j++)
for (int k=1; k<C; k++) //把x、y看成定值,累加z方向
D[num(i,j,k+1)] += D[num(i,j,k)];
for (int i=1; i<=A; i++)
for (int k=1; k<=C; k++)
for (int j=1; j<B; j++) //把x、z看成定值,累加y方向
D[num(i,j+1,k)] += D[num(i,j,k)];
for (int j=1; j<=B; j++)
for (int k=1; k<=C; k++)
for (int i=1; i<A; i++) //把y、z看成定值,累加x方向
D[num(i+1,j,k)] += D[num(i,j,k)];
for (int i=1; i<=n; i++) //最后判断是否攻击值大于生命值
if (D[i]>s[i])
return true;
return false;
}
int main() {
scanf("%d%d%d%d", &A, &B, &C, &m);
n = A*B*C;
for (int i=1; i<=n; i++) scanf("%d", &s[i]); //读生命值
for (int i=1; i<=m; i++) //读每次攻击的范围,用坐标表示
scanf("%d%d%d%d%d%d%d",&x1[i],&x2[i],&y1[i],&y2[i],&z1[i],&z2[i],&d[i]);
int L = 1,R = m; //经典的二分写法
while (L<R) { //对m进行二分,找到临界值。总共只循环了log(m)次
int mid = (L+R)>>1;
if (check(mid)) R = mid;
else L = mid+1;
}
printf("%d\n", R); //打印临界值
return 0;
}

浙公网安备 33010602011771号