算法竞赛入门经典 例题6-22

UVa11853

Paintball

在一个正方形区域中有若干个敌人,每个敌人的攻击范围为r,求是否可以在不被攻击的前提下横穿正方形区域。如果可以横穿,求出最靠近北侧的入口和出口。

看起来无从下手,但是画个图就明白了。如果没有可以相互掩护射击的敌人的攻击范围覆盖了从北到南的每一个点,那么就是可以横穿的——只需要从敌人攻击范围之间的缝隙穿过就可以了,所以可以根据是否存在从北到南的连通块判断是否存在解。

接下来就是找入口和出口了。如果某个连通块覆盖了北边界,那么可以看包含圆形的连通块是否覆盖了西边界,如果覆盖了,那么肯定是不能从西北区域进入了,只能从西北区域的南边界点进入;否者从西北角进入就可以了。根据这个思路,问题就转化为了求解所有覆盖北边界的连通块和西边界交点中最南侧的点。

至于uDebug上的用例,好像有些问题。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <sstream>
#include <vector>

using namespace std;

struct Opponent
{
	double x, y, r;
	bool cross(const Opponent &opponent) const
	{
		return pow(x - opponent.x, 2) + pow(y - opponent.y, 2)
			< pow(r + opponent.r, 2);
	}
};

struct Block
{
	vector<Opponent> opponents;
	double NorthBound, SouthBound;
	Block(const Opponent &opponent)
	{
		NorthBound = opponent.y + opponent.r;
		SouthBound = opponent.y - opponent.r;
		opponents.push_back(opponent);
	}
	void AddOpponent(const Opponent &opponent)
	{
		NorthBound = max(opponent.y + opponent.r, NorthBound);
		SouthBound = min(opponent.y - opponent.r, SouthBound);
		opponents.push_back(opponent);
	}
};

class Solution
{
public:
	Solution(const vector<Opponent> &opponents) : visited(opponents.size(), false)
	{
		for (size_t i = 0; i < opponents.size(); i++)
		{
			if (!visited[i]) {
				visited[i] = true;
				blocks.push_back(Block(opponents[i]));
				CountBlock(i, opponents);
			}
		}
	}
	string GetEntry()
	{
		double LeftY = 1000.0, RightY = 1000.0, y;
		ostringstream oss;
		for (const Block &block : blocks)
		{
			if (block.NorthBound > 1000.0) {
				if (block.SouthBound < 0.0) return "IMPOSSIBLE";
				else {
					for (const Opponent &opponent : block.opponents)
					{
						if (opponent.x - opponent.r < 0.0) {
							y = opponent.y - sqrt((pow(opponent.r, 2) - pow(opponent.x, 2)));
							LeftY = min(y, LeftY);
						}
						if (opponent.x + opponent.r > 1000.0) {
							y = opponent.y - sqrt((pow(opponent.r, 2) - pow(1000.0 - opponent.x, 2)));
							RightY = min(y, RightY);
						}
					}
				}
			}
		}
		oss << fixed << setprecision(2);
		oss << "0.00 " << LeftY << ' ' << "1000.00 " << RightY;
		return oss.str();
	}
private:
	vector<Block> blocks;
	vector<bool> visited;
	void CountBlock(size_t curr, const vector<Opponent> &opponents)
	{
		for (size_t i = 0; i < opponents.size(); i++)
		{
			if (!visited[i] && opponents[curr].cross(opponents[i])) {
				visited[i] = true;
				blocks.back().AddOpponent(opponents[i]);
				CountBlock(i, opponents);
			}
		}
	}
};

int main()
{
	size_t n = 0;
	while (cin >> n) {
		vector<Opponent> opponents;
		for (size_t i = 0; i < n; i++)
		{
			Opponent opponent;
			cin >> opponent.x >> opponent.y >> opponent.r;
			opponents.push_back(opponent);
		}
		Solution solution(opponents);
		cout << solution.GetEntry() << endl;
	}
	return 0;
}
/*
3
500 500 499
0 0 999
1000 1000 200
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值