UVa Problem 10117 Nice Milk (美味的牛奶)

// Nice Milk (美味的牛奶)
// PC/UVa IDs: 111408/10117, Popularity: C, Success rate: low Level: 4
// Verdict: Accepted
// Submission Date: 2011-11-15
// UVa Run Time: 0.456s
//
// 版权所有(C)2011,邱秋。metaphysis # yeah dot net
//
// [解题方法]
// 遍历所有可能的蘸牛奶方案,求中心未蘸到牛奶的最小面积,实质是半平面求交问题。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>

using namespace std;

#define MAXN 25
#define EPSILON (1E-7)

struct point
{
	double x, y;
};

// 使用直线和 X 轴所成的角度和直线经过的两个点的坐标来表示该条直线。
struct line
{
	point a, b;
	double angle;
};

// edgeLine 直线组指的是凸多边形相邻顶点之间的直线,innerLine 直线组指的是 edgeLine 向凸多
// 边形中心靠近 h 距离后的直线。
line edgeLine[MAXN], innerLine[MAXN], deque[MAXN], tmpLine[MAXN];
point bread[MAXN], innerBread[MAXN];
int innerN;
double minArea;
bool finished, dipped[MAXN];

double crossProduct(point a, point b, point c)
{
	return (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
}

// 从点 a 向点 b 望去,点 c 位于线段 ab 的右侧,返回 true。
bool cw(point a, point b, point c)
{
	return crossProduct(a, b, c) > EPSILON;
}
// 从点 a 向点 b 望去,点 c 位于线段 ab 的左侧时,返回 true。
bool ccw(point a, point b, point c)
{
	return crossProduct(a, b, c) < -EPSILON;
}

void pointsToLine(point a, point b, line& l)
{
	l.a = a;
	l.b = b;
	l.angle = atan2(a.y - b.y, a.x - b.x);
}

// 角度排序函数。
bool cmpAngle(line a, line b)
{
	return fabs(a.angle - b.angle) <= EPSILON;
}

// 半平面比较函数。
bool cmpHalfPlane(line f, line s)
{
	if (fabs(f.angle - s.angle) <= EPSILON)
		return crossProduct(s.a, s.b, f.a) < 0.0;

	return f.angle < s.angle;
}

// 点比较函数。
bool cmpPoint(point a, point b)
{
	return fabs(a.x - b.x) <= EPSILON && fabs(a.y - b.y) <= EPSILON;
}

// 两条直线是否平行。
bool paralleLine(line f, line s)
{
	return fabs((f.a.x - f.b.x) * (s.a.y - s.b.y) -
		(s.a.x - s.b.x) * (f.a.y - f.b.y)) <= EPSILON;
}

// 计算两点间距离。
double calDistance(point a, point b)
{
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

// 坐标变换。
void shiftPoint(point a, point b, point & c, point & d, double offset)
{
	double distance = calDistance(a, b);
	double dx = offset / distance * (a.y - b.y);
	double dy = offset / distance * (-a.x + b.x);
	c.x = a.x + dx;
	c.y = a.y + dy;
	d.x = b.x + dx;
	d.y = b.y + dy;
}

// 利用有向面积计算多边形的面积,注意最后结果取绝对值,因为顶点顺序可能并不是按逆时针方向给出。
double area(point p[], int n)
{
	if (n < 3)
		return 0.0;
	double total = 0.0;
	for (int i = 0, j = (i + 1) % n; i < n; i++, j = (i + 1) % n)
		total += (p[i].x * p[j].y - p[j].x * p[i].y);
	return fabs(total / 2.0);
}

point intersectionPoint(line f, line s)
{
	point p = f.a;
	double tmp =
		((f.a.x - s.a.x) * (s.a.y - s.b.y) - (f.a.y -
			s.a.y) * (s.a.x - s.b.x)) / ((f.a.x -
			f.b.x) * (s.a.y - s.b.y) - (f.a.y -
			f.b.y) * (s.a.x - s.b.x));
	p.x += (f.b.x - f.a.x) * tmp;
	p.y += (f.b.y - f.a.y) * tmp;
	return p;
}

// 给定一组直线,求直线的交点得到多边形的顶点。
void halfPlaneIntersection(line* edgeLine, int nLine, point* vertex, int& nPoint)
{
	nPoint = 0;
	sort(edgeLine, edgeLine + nLine, cmpHalfPlane);
	nLine = unique(edgeLine, edgeLine + nLine, cmpAngle) - edgeLine;

	int bottom = 0, top = 1;
	deque[0] = tmpLine[0];
	deque[1] = tmpLine[1];

	for (int i = 2; i < nLine; i++)
	{
		if (paralleLine(deque[top], deque[top - 1])
			|| paralleLine(deque[bottom], deque[bottom + 1]))
			return;

		while (bottom < top && cw(tmpLine[i].a, tmpLine[i].b,
			intersectionPoint(deque[top], deque[top - 1])))
		    top--;

		while (bottom < top && cw(tmpLine[i].a, tmpLine[i].b,
			intersectionPoint(deque[bottom], deque[bottom + 1])))
		    bottom++;

		deque[++top] = tmpLine[i];
	}

	while (bottom < top && cw(deque[bottom].a, deque[bottom].b,
		intersectionPoint(deque[top], deque[top - 1])))
	    top--;

	while (bottom < top && cw(deque[top].a, deque[top].b,
		intersectionPoint(deque[bottom], deque[bottom + 1])))
	    bottom++;

	if (top <= (bottom + 1))
		return;

	// 求相邻两条凸包边的交点获取顶点坐标。
	for (int i = bottom; i < top; i++)
		vertex[nPoint++] = intersectionPoint(deque[i], deque[i + 1]);

	// 首尾两条直线的交点也是顶点。
	if (bottom < (top + 1))
		vertex[nPoint++] = intersectionPoint(deque[bottom], deque[top]);

	// 去除重复的顶点。
	nPoint = unique(vertex, vertex + nPoint, cmpPoint) - vertex;
}

// 回溯以遍历可能的蘸牛奶方案。
void backtrack(int choice, int current, int target, int n)
{
	if ((n - choice) < (target - current))
		return;

	if (current == target)
	{
		memcpy(tmpLine, edgeLine, sizeof(edgeLine));
		for (int i = 0; i < n; i++)
			if (dipped[i])
				tmpLine[i] = innerLine[i];
		halfPlaneIntersection(tmpLine, n, innerBread, innerN);
		minArea = min(minArea, area(innerBread, innerN));
		if (minArea == 0.0)
			finished = true;
	}
	else
	{
		for (int i = choice + 1; i < n; i++)
			if (dipped[i] == false)
			{
				dipped[i] = true;
				backtrack(i, current + 1, target, n);
				dipped[i] = false;
				if (finished)
					return;
			}
	}
}

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

	int n, k, h;

	while (cin >> n >> k >> h, n || k || h)
	{
		for (int i = 0; i < n; i++)
			cin >> bread[i].x >> bread[i].y;

		// 牛奶深度为 0,或者蘸的次数为 0,则能蘸到的面积为 0。
		if (h == 0 || k == 0)
		{
			cout << "0.00" << endl;
			continue;
		}

		// 求预设的直线组 edgeLine 和 innerLine,直线组 innerLine 中直线由直线
		// 组 edgeLine 移动 h 距离而来。
		for (int i = 0, j = (i + 1) % n; i < n; i++, j = (i + 1) % n)
		{
			pointsToLine(bread[i], bread[j], edgeLine[i]);
			point c, d;
			shiftPoint(bread[i], bread[j], c, d, (double) (h));
			pointsToLine(c, d, innerLine[i]);
		}

		// 当蘸的次数大于边数时,重设蘸的次数。
		k = min(k, n);

		// 回溯遍历可能的蘸牛奶方案。
		finished = false;
		minArea = 1E20;
		memset(dipped, false, sizeof(dipped));
		backtrack(-1, 0, k, n);

		double allArea = area(bread, n);
		cout << (allArea - minArea) << endl;
	}

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值