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三者中有一个小于等于零,那么它就是无意义的,这时候直接返回零即可,相当于把操作执行在了不存在的点上,即舍弃了这个操作。