UVa Problem 10084 Hotter Colder (冷热游戏)

// Hotter Colder (冷热游戏)
// PC/UVa IDs: 111404/10084, Popularity: C, Success rate: low Level: 3
// Verdict: Accepted
// Submission Date: 2011-11-09
// UVa Run Time: 0.008s
//
// 版权所有(C)2011,邱秋。metaphysis # yeah dot net
//
// [解题方法]
// 本题的实质是半平面与凸多边形求交问题,表示半平面的直线即相邻两个位置连线的垂直平分线。由于初始
// 区域为凸多边形,每次切割后仍为凸多边形。

#include <iostream>
#include <iomanip>
#include <cmath>
#include <queue>

using namespace std;

#define MAXPOLY 60

#define EPSILON (1E-7)

#define SAME_LINE 0
#define PARALLE_LINE 1
#define LINE_NO_INTERSECT 2
#define LINE_INTERSECT 3

#define LIE_ON_LINE 0
#define BELOW_LINE (-1)
#define ABOVE_LINE 1

struct point
{
	double x;
	double y;
};

struct polygon
{
	int n;
	point p[MAXPOLY];
};

struct line
{
	double a, b, c;
};

struct segment
{
	point p1, p2;
};

polygon region;
double lastX = 0.0, lastY = 0.0, lastArea = 100.0;
bool isLineOrEmpty;

// 判断两条直线是否平行。
bool paralleLine(line l1, line l2)
{
	return ((fabs(l1.a - l2.a) <= EPSILON) && (fabs(l1.b - l2.b) <= EPSILON));
}

// 判断两条直线是否重合。
bool sameLine(line l1, line l2)
{
	return (paralleLine(l1, l2) && (fabs(l1.c - l2.c) <= EPSILON));
}

// 点是否在线段的包围盒内。
bool point_in_box(point p, point b1, point b2)
{
	return ((p.x >= min(b1.x, b2.x)) && (p.x <= max(b1.x, b2.x))
		&& (p.y >= min(b1.y, b2.y)) && (p.y <= max(b1.y, b2.y)));
}

// 求两直线的交点。
int intersection_point(line l1, line l2, segment s, point &p)
{
	if (sameLine(l1, l2))
		return SAME_LINE;

	if (paralleLine(l1, l2))
		return PARALLE_LINE;

	p.x = (l2.b * l1.c - l1.b * l2.c) / (l2.a * l1.b - l1.a * l2.b);
	if (fabs(l1.b) > EPSILON)
		p.y = -(l1.a * p.x + l1.c) / l1.b;
	else
		p.y = -(l2.a * p.x + l2.c) / l2.b;
		
	if (point_in_box(p, s.p1, s.p2))
		return LINE_INTERSECT;
	else
		return LINE_NO_INTERSECT;
}

// 通过两点求直线方程。
void points_to_line(point p1, point p2, line &l)
{
	if (p1.x == p2.x)
	{
		l.a = 1;
		l.b = 0;
		l.c = -p1.x;
	}
	else
	{
		l.b = 1;
		l.a = -(p1.y - p2.y) / (p1.x - p2.x);
		l.c = -(l.a * p1.x) - (l.b * p1.y);
	}
}

// 使用斜率和一点求直线方程。
void point_and_slope_to_line(point p, double m, line &l)
{
	l.a = -m;
	l.b = 1;
	l.c = -((l.a * p.x) + (l.b * p.y));
}

// 利用有向面积计算多边形的面积,注意最后结果取绝对值,因为顶点顺序可能并不是按逆时针方向给出。
double calArea(point vertex[], int vertexNumber)
{
	double total = 0.0;

	for (int i = 0; i < vertexNumber; i++)
	{
		int j = (i + 1) % vertexNumber;
		total += (vertex[i].x * vertex[j].y - vertex[j].x * vertex[i].y);
	}

	return fabs(total / 2.0);
}

// 判断点和直线的关系。
int relationPointLine(line l, point p)
{
	if (fabs(l.a * p.x + l.b * p.y + l.c) <= EPSILON)
		return LIE_ON_LINE;

	if ((l.a * p.x + l.b * p.y + l.c) > 0)
		return ABOVE_LINE;
	else
		return BELOW_LINE;
}

// 求半平面和凸多边形交的面积。
double area(double x, double y, string status)
{
	// 若坐标未变化,则返回最后计算所得到的面积。
	if (x == lastX && y == lastY)
		return lastArea;

	// 当可能区域为一条线段或者不可能存在这样的区域时,返回 0 值。
	if (isLineOrEmpty)
		return 0.0;

	// 若给出的是 Same,且前后位置坐标不同,则物品在线段的垂直平分线上。
	if (status == "Same")
	{
		isLineOrEmpty = true;
		return 0.0;
	}

	// 求端点为(x,y),(lastX,lastY)的线段的垂直平分线直线方程。
	line l1, l2;

	point middle;
	middle.x = (lastX + x) / 2.0;
	middle.y = (lastY + y) / 2.0;

	if (lastY == y)
	{
		l1.a = 1;
		l1.b = 0;
		l1.c = -middle.x;
	}
	else
	{
		double slope = -(lastX - x) / (lastY - y);
		point_and_slope_to_line(middle, slope, l1);
	}

	// 判断是那个半平面与凸多边形相交。可以通过起点和垂直平分线的关系以及提示语来判断。若
	// upPlane 为真,表示 a * x + b * y + c > 0 的半平面与凸多边形相交,反之则是另外
	// 一个半平面与之相交。
	bool upPlane = ((l1.a * x + l1.b * y + l1.c) > 0);
	if (status == "Colder")
		upPlane = !upPlane;

	// 判断直线与多边形的相交情况,总是顺时针枚举凸包的顶点,以便生成的凸多边形顶点顺序仍然
	// 是顺时针方向。
	queue < point > up, down;
	for (int i = 0; i < region.n; i++)
	{
		int j = (i + 1) % region.n;

		// 直线和点的位置关系。
		int ri = relationPointLine(l1, region.p[i]);
		int rj = relationPointLine(l1, region.p[j]);
		
		// 凸多边形两个相邻顶点在直线的异侧,表明直线和该线段有交点,求交点。
		if (ri * rj < 0)
		{
			points_to_line(region.p[i], region.p[j], l2);
			point intersect;
			segment s = (segment) { region.p[i], region.p[j] };
			intersection_point(l1, l2, s, intersect);
			
			if (ri < 0)
			{
				down.push(region.p[i]);
				down.push(intersect);
				up.push(intersect);
			}
			else
			{
				up.push(region.p[i]);
				up.push(intersect);
				down.push(intersect);
			}
		}
		// 凸多边形两个顶点或单个顶点在直线上。
		else if (ri * rj == 0)
		{
			if (ri == 0 && rj == 0)
			{
				up.push(region.p[i]);
				down.push(region.p[i]);
			}
			else if (ri == 0 && rj < 0)
			{
				up.push(region.p[i]);
				down.push(region.p[i]);
			}
			else if (ri == 0 && rj > 0)
			{
				down.push(region.p[i]);
				up.push(region.p[i]);
			}
			else if (ri < 0 && rj == 0)
				down.push(region.p[i]);
			else if (ri > 0 && rj == 0)
				up.push(region.p[i]);
		}
		// 凸多边形的两个顶点在直线的同侧。
		else
		{
			if (ri > 0)
				up.push(region.p[i]);
			else
				down.push(region.p[i]);
		}
	}

	// 根据所在半平面取相应的顶点。
	if (upPlane)
	{
		region.n = 0;
		while (!up.empty())
		{
			region.p[(region.n)++] = up.front();
			up.pop();
		}
	}
	else
	{
		region.n = 0;
		while (!down.empty())
		{
			region.p[(region.n)++] = down.front();
			down.pop();
		}	
	}

	// 计算面积。
	return calArea(region.p, region.n);
}

int main (int argc, char const* argv[])
{
	double x, y;
	string status;
	
	cout.precision(2);
	cout.setf(ios::fixed | ios::showpoint);

	// 初始凸多边形即为给定的正方形范围顶点。	
	region.n = 4;
	region.p[0] = (point) { 0.0, 0.0 };
	region.p[1] = (point) { 0.0, 10.0 };
	region.p[2] = (point) { 10.0, 10.0 };
	region.p[3] = (point) { 10.0, 0.0 };

	lastX = lastY = 0.0;

	isLineOrEmpty = false;

	// 读入位置和结果。	
	while (cin >> x >> y >> status)
	{
		cout << area(x, y, status) << endl;
		lastX = x;
		lastY = y;
	}

	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值