暑期集训中做了 POJ 2187 题,噩梦一场,但是这次仔细把一些 Graham 算法在处理存在三点在一条直线上问题的改进方法找到了。
题目意思很明确:给你平面上的一坨点,求最远两点距离的平方。
思路很容易,很多的解题报告上都有阐述,仅两步:1. 求凸包; 2. 旋转卡壳。
我们都知道,Graham 算法是二维凸包的一种相当好写的算法。但是当有三点一线的情况,此种算法就有可能出现问题。这个坑,在我看来,相当难查。
先贴一段 Graham 算法的代码。
bool cmp(point a, point b)
{
return direction(p[0], a, b) < 0;
}
void getConvex()
{
swap(p[0], p[Basis]);
Basis = 0;
sort(p + 1, p + n, cmp);
Top = 0;
p[n] = p[0];
convex[0] = p[0];
convex[1] = p[1];
if (n == 2)
return;
Top = 1;
for (int i = 2; i <= n; i++)
{
while (Top > 0 && direction(convex[Top - 1], convex[Top], p[i]) >= 0)
{
Top--;
}
convex[++Top] = p[i];
}
}
当此代码遇到有三点一线的情况,会出现什么问题呢?
先看下图,比较和谐的情况。
排序后按极角从小到达给编上号。首先 0,1 入栈 然后 2 入栈后由于 0-1-2 方向不是向左,于是将 1 弹出
再看一个不那么尽如人意的例子。我们知道,快速排序对于两个关键字相等的元素,其排序后的先后顺序是不可预知的。很有可能出现以下情况。
注意此时 0 1 入栈,按顺序 0 - 1 - 2 判断 显然 0 - 1 - 2 不是左转(用上述 cmp 函数判断为直线)。于是,万恶就此开始。1 被抛弃了。
显然这样凸包就求错了。
两种解决方案:1. 改用 Jarvis 步进法;2. 改进 cmp 函数。
改进 cmp 函数的方法是这样的:极角相同时,认为离基准点近的小,排在前面。
于是一切情况就转为和谐情况。并且此凸包上包含的点一定是最少的。
很多解题报告改变了弹栈的条件。直线情况不弹栈。我认为这样凸包上的点会略多,对后续的操作显得有些累赘了。
极角排序代码如下:
bool cmp(point a, point b)
{
if (direction(p[0], a, b) == 0)
return SQ(a - p[0]) < SQ(b - p[0]);
return direction(p[0], a, b) < 0;
}
SQ 代表向量的模的平方。p[0] 为基准点。
旋转卡壳什么的我就不贴代码了,不缺我这份。希望此日志能对陷在 POJ 2187 的同志们一些帮助……
此题卡我良久……同时感觉收获良多……特拿出来与大家分享。
--penhu, ShadowSword.
2012.8