// 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;
}
UVa Problem 10117 Nice Milk (美味的牛奶)
最新推荐文章于 2020-01-11 20:34:56 发布