【题解】洛谷P5506 封锁

题目链接:P5506 封锁

题解

  根据要求模拟飞机的运动即可。对于飞机“正前方”的理解有点难度,需要一定的时间理顺。然后,就可以愉快地写代码了。定义一个结构体 struct Plane 存储飞机的各种属性及其操作,除了题目中涉及的属性,我在结构体中额外增加了 xdirydirzdir 三个属性,描述飞机前进的方向向量在 x x x, y y y, z z z 三个坐标轴上的分量。
  对于给定的飞机,如何判断另一架飞机是否在它的正前方是一个棘手的问题,也是我在代码实现过程中,碰到的最大的困难。具体地,可以这样处理:
  记给定飞机所在位置为 P 1 P_1 P1,该飞机的“正前方”对应的方向向量为 d → \overrightarrow{d} d (由 xdirydirzdir 确定),另一架飞机用点 P 2 P_2 P2 表示,考察 d → \overrightarrow{d} d P 1 P 2 → \overrightarrow{P_1P_2} P1P2 点积叉积即可。
  飞机 P 2 P_2 P2 在飞机 P 1 P_1 P1 的正前方,当且仅当

d → × P 1 P 2 → = 0 →   ∧   d → ⋅ P 1 P 2 → > 0. \overrightarrow{d}\times \overrightarrow{P_1P_2}=\overrightarrow{0}\ \wedge\ \overrightarrow{d}\cdot \overrightarrow{P_1P_2}\gt0. d ×P1P2 =0   d P1P2 >0.
  叉积为 0 → \overrightarrow{0} 0 保证了两个向量共线,在此基础上,点积为 0 0 0 又使得两个向量方向相同,它们的点积与飞机间的距离 ∣ P 1 P 2 → ∣ |\overrightarrow{P_1P_2}| P1P2 成正比,因此当 P 2 P_2 P2 在正前方时,可以直接通过向量点积反映距离的远近,无需特别计算。上述功能通过结构体中 judge 成员函数实现。
  实现的代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>

const int dx[8] = {1, 1, 0, -1, -1, -1, 0, 1};
const int dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
const int dh[5] = {-1, -1, 0, 1, 1};

struct Plane {
	int x, y, z, h, f;
	int atk, def, mat, mdf, hp, fix;
	int xdir, ydir, zdir;
	char op[102];
	
	// 根据h, f,更新当前的坐标(前进一步) 
	void updateCoord() {
		if (h != 0 && h != 4) {
			xdir = dx[f];
			ydir = dy[f];
		} else {
			xdir = 0;
			ydir = 0;
		}
		zdir = dh[h];
		
		x += xdir;
		y += ydir;
		z += zdir;
	}
	
	// 判断另一个飞机是否在该飞机前面
	// 在前面, 则返回向量的点积(与距离成正比), 否则返回-1; 
	int judge(const Plane& t) {
		int xdi = t.x - x, ydi = t.y - y, zdi = t.z - z;
		if (xdi == 0 && ydi == 0 && zdi == 0) return 0; 
		int cs1, cs2, cs3, dot;
		cs1 = xdi * ydir - ydi * xdir;
		cs2 = ydi * zdir - zdi * ydir;
		cs3 = zdi * xdir - xdi * zdir;
		dot = xdi * xdir + ydi * ydir + zdi * zdir;
		if (cs1 != 0 || cs2 != 0 || cs3 != 0 || dot <= 0)	return -1;
		return dot;
	}
} p[103];

int n, t;

void init() {
	scanf("%d%d", &n, &t);
	for (int i = 0; i < n; ++i) {
		scanf("%d%d%d%d%d%d%d%d%d%d%d%s", &p[i].x, &p[i].y, &p[i].z, &p[i].h, &p[i].f, &p[i].atk, &p[i].def, &p[i].mat, &p[i].mdf, &p[i].hp, &p[i].fix, p[i].op);
	}
}

// 模拟时刻为 time 时,飞机的行为 
void simulateOneRound(int time) {
	for (int i = 0; i < n; ++i)
		if (p[i].hp > 0) p[i].updateCoord();
	for (int i = 0; i < n; ++i) {
		if (p[i].hp <= 0) continue;	
		switch (p[i].op[time]) {
			case 'N': break;
			case 'U': if (p[i].h < 4) ++p[i].h; break;
			case 'D': if (p[i].h > 0) --p[i].h; break;
			case 'L': if (++p[i].f > 7) p[i].f = 0; break;
			case 'R': if (--p[i].f < 0) p[i].f = 7; break;
			case 'F': p[i].hp += p[i].fix; break;
			case 'A': {
				int loc = -1, mndis;
				for (int j = 0; j < n; ++j) {
					if (p[j].hp <= 0 || j == i) continue;
					int dis = p[i].judge(p[j]);
					if (dis >= 0) {
						if (loc == -1 || dis < mndis)
							loc = j, mndis = dis;
					}
				}
				if (loc != -1 && mndis > 0)	p[loc].hp -= std::max(0, p[i].atk - p[loc].def);
				break;
			}
			case 'M':
				for (int j = 0; j < n; ++j) {
					if (p[j].hp <= 0) continue;
					if (p[i].judge(p[j]) > 0) {
						p[j].hp -= std::max(0, p[i].mat - p[j].mdf);
					} 
				}
				break;
		}
	}
}

int main() {
	init();
	
	for (int i = 0; i < t; ++i)
		simulateOneRound(i); 
	
	for (int i = 0; i < n; ++i)
		printf("%d %d %d %d\n", p[i].x, p[i].y, p[i].z, std::max(0, p[i].hp));
	
	return 0;
} 

总结

  这道模拟题还是很有意思的,在题意理解,飞机位置判断以及细节处理上都对我们提出了一定的要求。我写完这题的代码,经过一番调试,发现过不了样例 2 2 2,对此,我百思不得其解。于是,在讨论区里请教了一下提供该样例的大佬,十几分钟后就得到了回复,第一次在洛谷社区参与到了和题目有关的交流,得到了耐心的解答,还是挺开心的,附上截图,顺便帮看到的人避避坑。
截图

题外话

  其实最近还写了好几道其他模拟题,但是因为时间原因,加上自己实现的代码不如别人的简洁等因素,不想专门写题解了。这里简单提一提感受。

P4872 OIer们的东方梦
  说实话,读完题目感觉要素挺多,还是有点难 (恶心) 的。
  考虑什么时候需要往回走是该题的难点所在。经过分析,玩家的状态可以分为三种初始状态、仅获得太阳花后的状态、获得楼观剑后的状态。这三个状态人物的能力是由弱到强的,所以只有当人物状态发生转变时,才有可能需要往回走。我们把网格图分为三层,用优先队列进行BFS即可。

P5006 [yLOI2018] 大美江湖
  模拟水题。做过这么多模拟题里最简单的一个。直接分情况处理,就不解释了。

P5756 [NOI2000] 程序分析器
  模拟语句的执行本身不难,让我困惑的是如何判断程序能否正常结束。想了不少方法,后面都被自己否定掉了,我以为这是需要用到深奥图论知识来处理的难题。
  看了一眼题解,居然还是暴力模拟,确立一个上限,语句的执行总次数超过上限,就判定为不能正常结束。好吧,果然是一道货真价实的模拟题呢。
  后来想了想,我们使用的编译器尚且无法找出程序中的死循环,对一个程序能否正常结束给出答案,这个问题应该也没有有效的算法来解决吧。这背后的渊源或许牵涉到图灵停机问题吧······也就是说,没有通用的算法判断程序能否正常结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值