三维差分—三体攻击

直达原题

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;
}

posted @ 2023-07-28 16:15  LongDz  阅读(41)  评论(0)    收藏  举报