三体攻击问题(三维 差分+前缀和)后篇【含有详细的题目解读】

三体攻击问题后篇

前言

三体攻击上篇
在上一篇博客当中,我们提到了三维前缀和的基本概念,求解公式的推导,联合了之前二维差分前缀和的知识。这篇博客将会介绍三体攻击这道题目的解法,其中每个部分都会有详细的讲解,感兴趣的小伙伴可以点个关注。

题目详情

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

为了抵御攻击,地球人派出了 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 的伤害。
如果一个战舰累计受到的总伤害超过其防御力,那么这个战舰会爆炸。
地球指挥官希望你能告诉他,第一艘爆炸的战舰是在哪一轮攻击后爆炸的。

输入格式
第一行包括 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

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

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

数据范围
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 开始。

输入样例:
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 点伤害,超出其防御力导致爆炸。

题目分析

地球人派出了 A×B×C艘战舰,在太空中排成一个 A 层 B 行 C 列的立方体。

我们可以判断这是个三维长方体结构,后续的操作会对长方体当中的单点进行修改
这里就要用到前缀和与差分的知识了。

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

目标中可以划分为 2 种状态,一种是第一艘战舰爆炸前的,另一种是爆炸后,并且状态度的过渡不可逆,故我们采用二分的手段判断是在什么时候爆炸

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

这里涉及到了高维度数组到低维度数组的映射
映射的手法如下,例如有个
a [ A] [ B ]的数组,映射到数组 s [ N ]上有 a [ i ] [ j ] = s [ i * B + j ]
那么同理,对于我们a [ A ] [ B ] [ C ]数组,有 a [ i ] [ j ] [ k ]=s [ (i*B+j)*C + k]
这么看来上面这句话是不是好理解多了,那为什么要-1呢?

1≤lat≤rat≤A
1≤lbt≤rbt≤B
1≤lct≤rct≤C

这里的坐标下标都是从 1开始,如果不减 1 就会多出一条长,宽,高,
+1是确保下标全部从1 开始
预备知识已经讲述好了。

代码【具体的解释全部在注释里面,预备的知识可以参考前言当中的链接】

注释查看建议用下图的方法:点击鼠标左键后拉取观看即可
在这里插入图片描述

友情提醒:建议小伙伴先从main函数的内容先开始看起

#include<iostream>
#include<cstring>
using namespace std;
const int N =2e6+7;
//为什么要开成 2e6 呢?
//假设我们多出了一条长,宽,高,那我们就会相应的多出一个体积,为了防止
//数组越界,我们在这里用 2 * N 来进行储存
int A,B,C,m;
typedef long long LL;
//由于涉及到前缀和,我们这里要采用long long 去存储
LL s[N],b[N],bp[N];
//s[ N ]代表前缀和
// b[ N ]代表差分
//bp [ N ]代表差分数组的备份
//为什么要进行备份呢?
//在每次二分查找的操作当中,我们都需要一个纯净的原数组 b [ N ]
//也就是还没有进行过修改操作的差分数组 b [N]   
int op[N/2][7];
int d[8][4]{//为了方便后续的操作,这里我们将 8 个方向操作手法以及
//需要 + - 的坐标直接写到数组里面,
//前三个数是 分别记录 x ,y ,z的坐标变化
//第四个数是需要 加减的符号
    {0,0,0, 1},
    {0,0,1,-1},
    {0,1,0,-1},
    {0,1,1, 1},
    {1,0,0,-1},
    {1,0,1, 1},
    {1,1,0, 1},
    {1,1,1,-1},
};
int get(int i,int j,int k){
    return (i*B+j)*C+k;
}//有题意可得,需要对三维的数组进行映射操作;
bool check(int mid){
    memcpy(b,bp,sizeof bp);
    //首先从备份数组当中获取纯净的差分数组 b 
    //下面的代码是读入修改的数据,进行范围操作
    //操作的方向是包括自己在内的 8 个方向
    //这个在前言中的链接中记载了详细的方法和思路
    for(int i=1;i<=mid;i++){
        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],h=op[i][6];
        b[get(x1  ,y1,  z1)]  -=h;
        b[get(x2+1,y1,  z1)]  +=h;
        b[get(x1  ,y2+1,z1)]  +=h;
        b[get(x2+1,y2+1,z1)]  -=h;
        b[get(x1  ,y1  ,z2+1)]+=h;
        b[get(x2+1,y1  ,z2+1)]-=h;
        b[get(x1  ,y2+1,z2+1)]-=h;
        b[get(x2+1,y2+1,z2+1)]+=h;
    }
    //修改完之后清空前缀和数组重新前缀和计算
    memset(s,0,sizeof s);
    for(int i=1;i<=A;i++){
        for(int j=1;j<=B;j++){
            for(int k=1;k<=C;k++){
                s[get(i,j,k)]=b[get(i,j,k)];//提前为前缀和数组赋值
                //如果不赋值,后面的 u 就从 0 开始循环
               for(int u=1;u<8;u++){
                   int x=i-d[u][0],y=j-d[u][1],z=k-d[u][2],t=d[u][3];
                    s[get(i,j,k)]-=s[get(x,y,z)]*t;
                } 
                if(s[get(i,j,k)]<0) return true;
                //计算出的差分数组的前缀和如果小于 0 ,那么返回 true
            }
        }
    }
    return false;
}
int main(){
    cin>>A>>B>>C>>m;
    for(int i=1;i<=A;i++){
        for(int j=1;j<=B;j++){
            for(int k=1;k<=C;k++){
                cin>>s[get(i,j,k)];
            }
        }
    }
    //上面的都是输入操作
    //-----------------
    //这一段代码是差分数组的构建,也是单点修改
    for(int i=1;i<=A;i++){
        for(int j=1;j<=B;j++){
            for(int k=1;k<=C;k++){
                for(int u=0;u<8;u++){
                //每个点要接受来自包括自己在内的 8 个方向的修改
                //至于为什么是来自 8 个方向,这个在上篇文章中提到了
                    int x=i-d[u][0],y=j-d[u][1],z=k-d[u][2],t=d[u][3];
                    bp[get(i,j,k)]+=s[get(x,y,z)]*t;
                }
            }
        }
    }
    //下面的是修改信息的读取
    for(int i=1;i<=m;i++){
        for(int j=0;j<7;j++){
            cin>>op[i][j];
        }
    }
    //最后是我们熟悉的二分查找
    int l=1,r=m;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    cout<<l<<endl;
    return 0;
}

总结

以上就是 三体攻击问题的全部内容了,获取修改公式的方法主要是将三维转化为二维这个在上篇提到过,之后我将会将 2 篇的内容整合再次优化。喜欢的小伙伴记得点个赞+关注啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蒜白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值