Acwing 1232.三体攻击

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

为了抵御攻击,地球人派出了 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 点伤害,超出其防御力导致爆炸。


分析
本题是三维差分的一个题目。
前缀和、差分 是互为逆操作的。假设原序列为s,差分序列为b:

如果是一维差分
s[i] = b[i] + s[i-1];
因此有:b[i] = s[i] - s[i-1]
此时若将区间s[l…r]中都加上一个数c,对数组b的影响:
b[l] = s[l] - s[l-1] => b[l] += c;
b[r+1] = s[r+1] - s[r] => b[r+1] -= c;


如果是二维差分:
s[i][j] = b[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1];
因此有:b[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1]
若将位于(x1, y1)和(x2, y2)之间的原序列都加上c,对数组b的影响:
b[x1][y1] += c;
b[x1][y2+1] -= c;
b[x2+1][y1] -= c;
b[x2+1][y2+1] += c;


如果是三维差分:
s[i][j][k] = b[i][j][k] + s[i-1][j][k] + s[i][j-1][k] - s[i-1][j-1][k]
+ s[i][j][k-1] - s[i-1][j][k-1] - s[i][j-1][k-1] + s[i-1][j-1][k-1];
因此可以根据原数组s推出b:
b[i][j][k] = s[i][j][k] - s[i-1][j][k] - s[i][j-1][k] + s[i-1][j-1][k]
- s[i][j][k-1] + s[i-1][j][k-1] + s[i][j-1][k-1] - s[i-1][j-1][k-1];

可以发现,在求b[i][j][k]的等式右侧,有偶数个坐标减一前面的系数为+1,奇数个坐标减一系数为-1。
现在考虑将位于(x1, y1, z1)和(x2, y2, z2)之间的原序列都加上c,对数组b的影响:

b[x1    ][y1    ][z1    ]   += c;  // 000
b[x1    ][y1    ][z2 + 1]   -= c;  // 001
b[x1    ][y2 + 1][z1    ]   -= c;  // 010
b[x1    ][y2 + 1][z2 + 1]   += c;  // 011
b[x2 + 1][y1    ][z1    ]   -= c;  // 100
b[x2 + 1][y1    ][z2 + 1]   += c;  // 101
b[x2 + 1][y2 + 1][z1    ]   += c;  // 110
b[x2 + 1][y2 + 1][z2 + 1]   -= c;  // 111

可以发现有偶数个加一的就是+=c,有奇数个加一的就是-=c。
因为操作次数越多,对战舰的伤害越大,因此可以使用二分求解答案。

#include<iostream>
#include<cstring>

using namespace std;

typedef long long LL;

const int N = 2000020;

int A, B, C, m;
LL s[N]; //原数组
LL b[N], bp[N]; //差分数组

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 op[N / 2][7];

int get(int i, int j, int k)
{
	return (i * B + j) * C + k;
}

bool check(int mid)
{
	memcpy(b, bp, sizeof bp);
	for(int i = 1; i <= mid; i ++)
	{
		// 给(x1, y1, z1), (x2, y2, z2)之间加上c
		int x1 = op[i][0], x2 = op[i][1];
		int y1 = op[i][2], y2 = op[i][3];
		int z1 = op[i][4], z2 = op[i][5];
		int c = -op[i][6];
		b[get(x1    , y1    , z1)]      += c;
        b[get(x1    , y1    , z2 + 1)]  -= c;
        b[get(x1    , y2 + 1, z1)]      -= c;
        b[get(x1    , y2 + 1, z2 + 1)]  += c;
        b[get(x2 + 1, y1    , z1)]      -= c;
        b[get(x2 + 1, y1    , z2 + 1)]  += c;
        b[get(x2 + 1, y2 + 1, z1)]      += c;
        b[get(x2 + 1, y2 + 1, z2 + 1)]  -= c;
	}
	
	//根据差分数组求解原数组
	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()
{
	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)];
			}
		}
	} 
	
	//根据原数组s计算差分数组 
	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 ++)
		{
			cin >> op[i][j];
		}
	}
	
	int l = 1, r = m;
	while(l < r)
	{
		int mid = (l + r) / 2;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	
	cout << r << endl;
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值