P8666 [蓝桥杯 2018 省 A] 三体攻击

这份代码开了O2会WA和RE,但不开的话又会因为STL速度太慢而TLE

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#define For(i, j, n) for (int i = j; i <= n; ++i)
using namespace std;

const int N = 2e6 + 10;
typedef long long LL;
typedef pair<LL, int> PLI;

int A, B, C, m, num, now, opidx[N]; // now记录当前是第几次操作,opidx记录每个点的最后一次操作处于第几次
vector<PLI> ops[N/2]; // 记录操作序列
LL nhp[N];

int xytok(int x, int y, int z)
{
    return ((x - 1) * B + (y - 1)) * C + z;
}

void op_push(int k, int h)
{
    //printf("op push : %d, %d\n", k, h);
    if(ops[k].empty())
    {
        ops[k].push_back({(LL)h, now});
        opidx[k] = now;
        return ;
    }
    if(opidx[k] != now)
    {
       ops[k].push_back({(LL)h, now});
    }
    else
    {
        ops[k].back().first += (LL)h;
    }
    opidx[k] = now;
    //nhp[k] += (LL)h;
}

void Operate(int x1,int y1, int z1, int x2, int y2, int z2, int h)
{
    op_push(xytok(x1, y1, z1), h);
    op_push(xytok(x2 + 1, y1, z1), -h);
    op_push(xytok(x1, y1, z2 + 1), -h);
    op_push(xytok(x2 + 1, y1, z2 + 1), h);

    op_push(xytok(x1, y2 + 1, z1), -h);
    op_push(xytok(x2 + 1, y2 + 1, z1), h);
    op_push(xytok(x1, y2 + 1, z2 + 1), h);
    op_push(xytok(x2 + 1, y2 + 1, z2 + 1), -h);
}

void add(LL h[], int x, int y, int z)
{
    h[xytok(x, y, z)] += h[xytok(x - 1, y, z)] + h[xytok(x, y - 1, z)] + h[xytok(x, y, z - 1)]
                  - h[xytok(x, y - 1, z - 1)] - h[xytok(x - 1, y, z - 1)] - h[xytok(x - 1, y - 1, z)]
                  + h[xytok(x - 1, y - 1, z - 1)];
}

int find(vector<PLI> &v, int x)
{
    int l = 0, r = v.size() - 1;
    while(l < r)
    {
        int mid = l + r + 1 >> 1;
        if(v[mid].second <= x) l = mid;
        else r = mid - 1;
    }
    return l;
}

bool check(int x)
{
    for(int i = 1; i <= num; i++)
    {
        int pos = find(ops[i], x);
        nhp[i] = ops[i][pos].first;
    }
    /*for(int i = 1; i <= num; i++)
        printf("%d ", nhp[i]);
    puts("");*/
    for(int i = 1, cnt = 1; i <= A; i++)
        for(int j = 1; j <= B; j++)
            for(int k = 1; k <= C; k++, cnt++)
            {
                add(nhp, i, j, k);
                if(nhp[cnt] < 0ll)
                {
                    //printf("destroyed at (%d, %d, %d, %d)\n", i, j, k, x);
                    return true;
                }
            }
    return false;
}

int search()
{
    int l = 1, r = m;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main()
{
    int tmp;
    scanf("%d%d%d%d", &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++, num++)
            {
                scanf("%d", &tmp);
                // 把初始化也看成一次操作,就不需要特判了
                Operate(i, j, k, i, j, k, tmp);
            }
    now++;
    int x1, y1, z1, x2, y2, z2, h;
    for (int i = 1; i <= m; i++, now++)
    {
        scanf("%d%d%d%d%d%d%d", &x1, &x2, &y1, &y2, &z1, &z2, &h);
        Operate(x1, y1, z1, x2, y2, z2, -h);
    }
    /*printf("size:\n");
    for(int i = 1; i <= num; i++)
        printf("%d ", ops[i].size());
    puts("");*/
    for(int i = 1; i <= num; i++)
            for(int j = 1; j < ops[i].size(); j++)
                ops[i][j].first += ops[i][j - 1].first;
    /*check(2);
    for(int i = 1; i <= num; i++)
        printf("%lld ", nhp[i]);
    puts("");
    exit(0);*/
    printf("%d\n", search());
    return 0;
}

三维差分+二分答案

这是AC代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long  LL;
const int N = 2000010;

int A, B, C, m;// 层,行,列, 攻击轮数
LL s[N], b[N], bp[N];// 生命值, 差分数组,备份差分数组
int op[N / 2][7];// 攻击范围及伤害

// 差分数组八个方向偏移量
int d[8][4] = 
{
    {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 max(0,((i-1) * B + j-1) * C + k);
}
bool check(int mid)
{
    memcpy(b, bp, sizeof b);
    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(x1,     y1,     z2 + 1)] -= -h;
        b[get(x1,     y2 + 1,     z1)] -= -h;
        b[get(x1,     y2 + 1, z2 + 1)] += -h;
        b[get(x2 + 1, y1,         z1)] -= -h;
        b[get(x2 + 1, y1,     z2 + 1)] += -h;
        b[get(x2 + 1, y2 + 1,     z1)] += -h;
        b[get(x2 + 1, y2 + 1, z2 + 1)] -= -h;
        
    }
   
    /* 求前缀和

     s[i][j][k] = s[i-1][j][k]+s[i][j-1][k]+s[i][j][k-1]-s[i-1][j-1][k]-s[i-1][j][k-1]-s[i][j-1][k-1]+s[i-1][j-1][k-1]+b[i][j][k]

     */
    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)];
                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;
            }

    return false;

}
int main()
{

    scanf("%d%d%d%d", &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 ++ )
                scanf("%lld", &s[get(i, j, k)]);

    // 求差分数组 b[]
    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 ++ )
                {
                    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 ++ )
            scanf("%d", &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;
    }

    printf("%d\n", r);


    return 0;
    
}

 这份代码不加O2优化能过:

#include<bits/stdc++.h>
#define For(i, j, n) for (int i = j; i <= n; ++i)
using namespace std;

const int N = 2e6 + 10;
typedef long long LL;
typedef pair<LL, int> PLI;

int A, B, C, m, num = 1, now, op[N / 2][7]; // now记录当前是第几次操作,opidx记录每个点的最后一次操作处于第几次
LL hp[N], dmg[N];

int xytok(int x, int y, int z)
{
    return ((x - 1) * B + (y - 1)) * C + z;
}

void Operate(LL b[], int x1, int y1, int z1, int x2, int y2, int z2, LL h)
{
    b[xytok(x1, y1, z1)] += h; // 伤害为 负
    b[xytok(x1, y1, z2 + 1)] -= h;
    b[xytok(x1, y2 + 1, z1)] -= h;
    b[xytok(x1, y2 + 1, z2 + 1)] += h;
    b[xytok(x2 + 1, y1, z1)] -= h;
    b[xytok(x2 + 1, y1, z2 + 1)] += h;
    b[xytok(x2 + 1, y2 + 1, z1)] += h;
    b[xytok(x2 + 1, y2 + 1, z2 + 1)] -= h;
}

void add(LL b[], int i, int j, int k)
{
        b[xytok(i, j, k)]+=
        b[xytok(i-1, j, k)]+
        b[xytok(i, j-1, k)]+
        b[xytok(i, j, k-1)]-    
        b[xytok(i-1, j-1, k)]-    
        b[xytok(i-1, j, k-1)]-    
        b[xytok(i, j-1, k-1)]+    
        b[xytok(i-1, j-1, k-1)];                
}

bool check(int x)
{
    memset(dmg, 0, sizeof dmg);
    for (int i = 1; i <= x; i++) // x1,x2,y1,y2,z1,z2,h
    {
        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];
        Operate(dmg, x1, y1, z1, x2, y2, z2, (LL)h);
    }
    for (int i = 1, cnt = 1; i <= A; i++)
        for (int j = 1; j <= B; j++)
            for (int k = 1; k <= C; k++, cnt++)
            {
                add(dmg, i, j, k);
                if (dmg[cnt] > hp[cnt])
                    return 1;
            }
    return 0;
}

int search()
{
    int l = 1, r = m;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    return r;
}

int main()
{
    int tmp;
    scanf("%d%d%d%d", &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++, num++)
            {
                scanf("%lld", &hp[num]);
                // Operate(bp, i, j, k, i, j, k, hp[num]);
            }
    num--;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 0; j < 7; j++)
            scanf("%d", &op[i][j]);
    }
    printf("%d\n", search());
    return 0;
}

 这份代码不开O2效率较低,开启O2后,在xytok函数中,只有加入max(0,...)才能通过:

#include<bits/stdc++.h>
#define For(i, j, n) for (int i = j; i <= n; ++i)
using namespace std;

const int N = 2e6 + 10;
typedef long long LL;
typedef pair<LL, int> PLI;

int A, B, C, m, num = 1, op[N / 2][7]; // now记录当前是第几次操作,opidx记录每个点的最后一次操作处于第几次
LL hp[N], dmg[N];

int xytok(int x, int y, int z)
{
    return max(0,((x - 1) * B + (y - 1)) * C + z);
}

void Operate(LL b[], int x1, int y1, int z1, int x2, int y2, int z2, LL h)
{
    b[xytok(x1, y1, z1)] += h; // 伤害为 负
    b[xytok(x1, y1, z2 + 1)] -= h;
    b[xytok(x1, y2 + 1, z1)] -= h;
    b[xytok(x1, y2 + 1, z2 + 1)] += h;
    b[xytok(x2 + 1, y1, z1)] -= h;
    b[xytok(x2 + 1, y1, z2 + 1)] += h;
    b[xytok(x2 + 1, y2 + 1, z1)] += h;
    b[xytok(x2 + 1, y2 + 1, z2 + 1)] -= h;
}

void add(LL b[], int i, int j, int k)
{
        b[xytok(i, j, k)]+=
        b[xytok(i-1, j, k)]+
        b[xytok(i, j-1, k)]+
        b[xytok(i, j, k-1)]-    
        b[xytok(i-1, j-1, k)]-    
        b[xytok(i-1, j, k-1)]-    
        b[xytok(i, j-1, k-1)]+    
        b[xytok(i-1, j-1, k-1)];                
}

bool check(int x)
{
    memset(dmg, 0, sizeof dmg);
    for (int i = 1; i <= x; i++) // x1,x2,y1,y2,z1,z2,h
    {
        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];
        Operate(dmg, x1, y1, z1, x2, y2, z2, (LL)h);
    }
    for (int i = 1, cnt = 1; i <= A; i++)
        for (int j = 1; j <= B; j++)
            for (int k = 1; k <= C; k++, cnt++)
            {
                add(dmg, i, j, k);
                if (dmg[cnt] > hp[cnt])
                    return true;
            }
    return false;
}

int search()
{
    int l = 1, r = m;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    return r;
}

int main()
{
    int tmp;
    scanf("%d%d%d%d", &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++, num++)
            {
                scanf("%lld", &hp[num]);
                // Operate(bp, i, j, k, i, j, k, hp[num]);
            }
    num--;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 0; j < 7; j++)
            scanf("%d", &op[i][j]);
    }
    printf("%d\n", search());
    return 0;
}

 经过一下午的对比,终于确认了问题所在:

xytok函数的锅

 

int xytok(int i, int j, int k)
{
    return max(0, ((i - 1) * B + j - 1) * C + k);
}

 

一定要加上这个max(0,...)

因为当我们在处理(x,y,z)这个点的前缀和时,只要其中有任何一个为1,就会因为这里的各种减一操作:

void add(LL b[], int i, int j, int k)
{
    b[xytok(i, j, k)]+=
    b[xytok(i-1, j, k)]+
    b[xytok(i, j-1, k)]+
    b[xytok(i, j, k-1)]-
    b[xytok(i-1, j-1, k)]-
    b[xytok(i-1, j, k-1)]-
    b[xytok(i, j-1, k-1)]+
    b[xytok(i-1, j-1, k-1)];
}

导致最后传入xytok函数的i,j,k中存在0,而只要i或者j为零,就会导致这个函数return一个负数

而事实上,因为我们的坐标是从1开始的,只要i,j,k三者中有一个小于等于零,那么它就是无意义的,这时候直接返回零即可,相当于把操作执行在了不存在的点上,即舍弃了这个操作。

 

posted @ 2024-02-10 13:37  Gold_stein  阅读(11)  评论(0编辑  收藏  举报