三维(差分+前缀和)应用

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

思路1:暴力求解

暴力求解的方法是依次进行每一轮攻击,然后更新攻击范围内的战舰的生命值,当第t轮某一个战舰的血量小于0,那么直接返回 t.

思路2:二分+差分+前缀和

利用二分,第一次连续攻击m/2轮,然后判断战舰的生命值,如果此时没有战舰爆炸,那么再往后攻击m/4轮;如果此时有战舰爆炸,那么往前恢复m/4轮。不断二分,直到找到是在第几轮攻击时导致战舰爆炸。
那么怎么实现连续攻击m/2轮?
利用差分数组,再求前缀和

一、前缀和的概念:

例如,给出一组数 arr[ 1 , 2 , 3 , 4 , 5 ]
其前缀和 Sn[ 1, 3 , 6 , 10 , 15 ]
即 Sn[i] = Sn[i-1] + arr[i];

二、前缀和的特点:

  1. O(N)的时间维护
    例如,arr[2] += 3;
    则Sn[ 1, 3 , 6+3 , 10+3 , 15+3 ]
  2. O(1)的时间获取区间和
    例如,求在 [2,4] 的区间内数值和
    即 Sn[4] - Sn[1] = 15 - 3 = 12

三、一维差分数组+前缀和:

在这里插入图片描述

四、二维差分数组+前缀和:
在这里插入图片描述
五、三维差分数组+前缀和:

1、三维差分数组
在这里插入图片描述
给定三个角的坐标la , ra , lb , rb , lc , rc和攻击力h
正面四个
arr[lc][lb][la] += h;
arr[rc+1][lb][la] -= h;
arr[lc][lb][ra+1] -= h;
arr[rc+1][lb][ra+1] += h;
左面补两个
arr[lc][rb+1][la] -= h;
arr[lc][rb+1][ra+1] += h;
上面再补两个
arr[rc+1][rb+1][la] += h;
arr[rc+1][rb+1][ra+1] -= h;
2、三维前缀和
在这里插入图片描述
画的很乱,建议各位自己试着画一画。
arr[i][j][k] += arr[i][j-1][k]; //后方
arr[i][j][k] += arr[i-1][j][k];//左侧
arr[i][j][k] += arr[i][j][k-1]; //下方
arr[i][j][k] -= arr[i-1][j-1][k]; //左后
arr[i][j][k] -= arr[i-1][j][k-1]; //左下
arr[i][j][k] -= arr[i][j-1][k-1]; //后下
arr[i][j][k] += arr[i-1][j-1][k-1]; //左后下

六、算法实现

  1. 暴力求解
    private static int[] arr;
    private static int[][] attacks;
    private static int A,B,C,m;
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        A = cin.nextInt();
        B = cin.nextInt();
        C = cin.nextInt();
        m = cin.nextInt();
        arr = new int[(A+1)*(B+1)*(C+1)];
        attacks = new int[m+1][7];
        //初始化战舰生命值
        for (int i = 1;i < A+1;i++){
            for (int j = 1;j < B+1;j++){
                for (int k = 1;k < C+1;k++){
                    arr[getIndex(i,j,k)] = cin.nextInt();
                }
            }
        }

        //执行攻击
        for (int k = 1; k < m+1; k++) {
            for (int m = 0;m < 7;m++) //读入攻击信息。
                attacks[k][m] = cin.nextInt();

            for (int i = attacks[k][0];i <= attacks[k][1];i++){
                for (int j = attacks[k][2]; j <= attacks[k][3]; j++) {
                    for (int l = attacks[k][4]; l <= attacks[k][5]; l++) {
                        arr[getIndex(i,j,l)] -= attacks[k][6];
                        if (arr[getIndex(i,j,l)] < 0) {
                            System.out.println();
                            System.out.println(k);
                            return;
                        }
                    }
                }
            }
        }

    }

    static int getIndex(int i, int j, int k){
        return (((i-1)*(B+1)+(j-1))*(C+1)+k);
    }
  1. 二分+差分+前缀和
private static int A, B, C, a, b, c, m;
    private static int[][] attack;//攻击
    private static int[] diff;//差分数组

    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        A = cin.nextInt();
        B = cin.nextInt();
        C = cin.nextInt();
        m = cin.nextInt();
        a = A + 1;
        b = B + 1;
        c = C + 1;
        attack = new int[m + 1][7];//m次攻击
        diff = new int[a * b * c];//差分数组(也是战舰生命值数组)

        cin.nextLine();//吃掉换行符
        //战舰生命值初始化(相当于给战舰回血)
        for (int i = 1; i < a; i++) {
            for (int j = 1; j < b; j++) {
                for (int k = 1; k < c; k++) {
                    op(i, i, j, j, k, k, cin.nextInt());
                }
            }
        }

        cin.nextLine();
        //输入每轮攻击范围和强度
        for (int i = 1; i <= m; i++) {
            for (int j = 0; j < 7; j++) {
                attack[i][j] = cin.nextInt();
            }
            cin.nextLine();
        }

        //二分攻击
        int l = 1, r = m, mid, lastMid = 0;
        while (l < r) {
            mid = (l + r) >> 1;
            if (mid > lastMid) {//说明战舰还没爆炸,需要继续进攻
                for (int i = lastMid + 1; i <= mid; i++)
                    op(attack[i][0], attack[i][1], attack[i][2], attack[i][3], attack[i][4], attack[i][5], -attack[i][6]);
            }
            if (mid < lastMid) {//说明已经有战舰爆炸,需要撤销进攻
                for (int i = mid + 1; i <= lastMid; i++)
                    op(attack[i][0], attack[i][1], attack[i][2], attack[i][3], attack[i][4], attack[i][5], attack[i][6]);
            }
            if (check())
                r = mid;
            else
                l = mid + 1;
            lastMid = mid;
        }

        System.out.println(r);
    }

    static boolean check() {//求前缀和
        int[] temp = new int[a * b * c];
        System.arraycopy(diff, 0, temp, 0, a * b * c);
        for (int i = 1; i < a; i++) {
            for (int j = 1; j < b; j++) {
                for (int k = 1; k < c; k++) {
                    if (j - 1 > 1)
                        diff[getIndex(i, j, k)] += diff[getIndex(i, j - 1, k)];
                    if (k - 1 > 1)
                        diff[getIndex(i, j, k)] += diff[getIndex(i, j, k - 1)];
                    if (i - 1 > 1)
                        diff[getIndex(i, j, k)] += diff[getIndex(i - 1, j, k)];
                    if (i - 1 > 1 && j - 1 > 1)
                        diff[getIndex(i, j, k)] -= diff[getIndex(i - 1, j - 1, k)];
                    if (i - 1 > 1 && k - 1 > 1)
                        diff[getIndex(i, j, k)] -= diff[getIndex(i - 1, j, k - 1)];
                    if (j - 1 > 1 && k - 1 > 1)
                        diff[getIndex(i, j, k)] -= diff[getIndex(i, j - 1, k - 1)];
                    if (i - 1 > 1 && j - 1 > 1 && k - 1 > 1)
                        diff[getIndex(i, j, k)] += diff[getIndex(i - 1, j - 1, k - 1)];

                    if (diff[getIndex(i, j, k)] < 0)//如果当前战舰爆炸了
                        return true;
                }
            }
        }
        return false;//所有战舰都没爆炸
    }

    static int getIndex(int i, int j, int k) {
        return (((i - 1) * (B + 1) + (j - 1)) * (C + 1) + k);
    }

    static void op(int la, int ra, int lb, int rb, int lc, int rc, int m) {
        //正面
        diff[getIndex(la, lb, lc)] += m;
        if (rb + 1 < b)
            diff[getIndex(la, rb + 1, lc)] -= m;
        if (ra + 1 < a)
            diff[getIndex(ra + 1, lb, lc)] -= m;
        if (ra + 1 < a && rb + 1 < b)
            diff[getIndex(ra + 1, rb + 1, lc)] += m;
        //左面
        if (rc + 1 < c)
            diff[getIndex(la, lb, rc + 1)] -= m;
        if (ra + 1 < a && rc + 1 < c)
            diff[getIndex(ra + 1, lb, rc + 1)] += m;
        //上面
        if (ra + 1 < a && rb + 1 < b && rc + 1 < c)
            diff[getIndex(ra + 1, rb + 1, rc + 1)] -= m;
        if (rb + 1 < b && rc + 1 < c)
            diff[getIndex(la, rb + 1, rc + 1)] += m;
    }
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值