Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 24254 | Accepted: 7403 |
Description
Input
Output
Sample Input
4 0 0 0 1 1 1 1 0
Sample Output
2
Hint
Source
凸包模板:
/******************************************** 计算凸包,输入点数组 p, 个数为 n , 输出点数组 ch. 函数返回凸包顶点数 输入不能有重复点。函数执行完之后输入点的顺序被破坏 如果不希望在凸包的边上有输入点,把两个 <= 改成 < 【== 表示两向量共线】 在精度要求高时建议用 dcmp 比较 const double eps = 1e-10; int dcmp(double x) { if(fabs(x) < eps) return 0; else return x < 0 ? -1 : 1; } ********************************************/ double ConvexHull(Point* p, int n, Point* ch) /** 基于水平的Andrew算法求凸包 */ { sort(p,p+n,cmp); /**先按照 x 从小到大排序, 再按照 y 从小到大排序*/ int m = 0; for(int i = 0; i < n; i++) /** 从前往后找,求"下凸包" */ {/**Cross <= 0有等于,去掉 重复的点*/ while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; ch[m++] = p[i]; } int k = m; for(int i = n-2; i >= 0; i--) /**从后往前找,求"上凸包" 形成完整的封闭背包*/ { while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; ch[m++] = p[i]; } if(n > 1) m--; /** 起点重复 */ return m; }
盗版的别人博客的模板,下面会贴上大牛的关于这个算法的分析,一般是能看懂的了,如果不能看懂的,记住就好了
int rotating_calipers(Point *ch, int m) /**旋转卡壳模板*/ { int q = 1; int ans = 0; ch[m] = ch[0]; /**凸包边界处理*/ for(int i = 0; i < m; i++) /**依次用叉积找出凸包每一条边对应的最高点*/ {/**同底不同高,每次用面积判断高的大小就可以了*/ while(Cross(ch[i+1]-ch[i], ch[q+1]-ch[i]) > Cross(ch[i+1]-ch[i], ch[q]-ch[i])) q = (q+1)%m; /**每条底上有两个点,所以要 max 两次*/ ans = max(ans, max(squarDist(ch[i], ch[q]), squarDist(ch[i+1], ch[q+1]))); } return ans;/**返回的也就是凸包的直径*/ }
/******************************************** 题意:给你 N 个点, 求所有点中最远两点距离 算法:凸包+暴力 思路:最远距离两个点一定在凸包上,建立好背包后,枚举凸包上的点就可以了 *********************************************/ #include<stdio.h> #include<math.h> #include<string.h> #include<algorithm> using namespace std; const int maxn = 50000+10; int n,m; struct Point{ double x,y; Point(){}; Point(double _x, double _y) { x = _x; y = _y; } Point operator - (const Point & B) const { return Point(x-B.x, y-B.y); } }p[maxn], ch[maxn]; bool cmp(Point p1, Point p2) { if(p1.x == p2.x) return p1.y < p2.y; return p1.x < p2.x; } int squarDist(Point A, Point B) /**距离的平方*/ { return (A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y); } double Cross(Point A, Point B) /**叉积*/ { return A.x*B.y-A.y*B.x; } void ConvexHull() /** 求凸包 */ { sort(p,p+n,cmp); /**先按照 x 从小到大排序, 再按照 y 从小到大排序*/ m = 0; for(int i = 0; i < n; i++) /** 从前往后找"下凸包" */ { while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; ch[m++] = p[i]; } int k = m; for(int i = n-2; i >= 0; i--) /**从后往前找"上凸包", 形成完整的封闭背包*/ { while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; ch[m++] = p[i]; } if(n > 1) m--; /** 起点重复*/ } int main() { while(scanf("%d", &n) != EOF) { if(n == 0) break; for(int i = 0; i < n; i++) scanf("%lf%lf", &p[i].x, &p[i].y); ConvexHull(); int ans = 0; for(int i = 0; i < m; i++) for(int j = i+1; j < m; j++) ans = max(ans,squarDist(ch[i], ch[j])); printf("%d\n", ans); } return 0; }
/******************************************** 2187 Accepted 972K 297MS C++ 1927B 2013-07-27 13:38:35 题意:给你 N 个点, 求所有点中最远两点距离 算法:凸包+旋转卡壳 思路:最远距离两个点一定在凸包上,建立好背包后,直接套用旋转卡壳找直径 *********************************************/ #include<stdio.h> #include<math.h> #include<string.h> #include<algorithm> using namespace std; const int maxn = 50000+10; int n,m; struct Point{ double x,y; Point(){}; Point(double _x, double _y) { x = _x; y = _y; } Point operator - (const Point & B) const { return Point(x-B.x, y-B.y); } }p[maxn], ch[maxn]; bool cmp(Point p1, Point p2) { if(p1.x == p2.x) return p1.y < p2.y; return p1.x < p2.x; } int squarDist(Point A, Point B) /**距离的平方*/ { return (A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y); } double Cross(Point A, Point B) /**叉积*/ { return A.x*B.y-A.y*B.x; } void ConvexHull() /** 基于水平的Andrew算法求凸包 */ { sort(p,p+n,cmp); /**先按照 x 从小到大排序, 再按照 y 从小到大排序*/ m = 0; for(int i = 0; i < n; i++) /** 从前往后找 */ { while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; ch[m++] = p[i]; } int k = m; for(int i = n-2; i >= 0; i--) /**从后往前找, 形成完整的封闭背包*/ { while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--; ch[m++] = p[i]; } if(n > 1) m--; } int rotating_calipers() /**旋转卡壳模板*/ { int q = 1; int ans = 0; ch[m] = ch[0]; /**凸包边界处理*/ for(int i = 0; i < m; i++) /**依次用叉积找出凸包每一条边对应的最高点*/ { while(Cross(ch[i+1]-ch[i], ch[q+1]-ch[i]) > Cross(ch[i+1]-ch[i], ch[q]-ch[i])) q = (q+1)%m; ans = max(ans, max(squarDist(ch[i], ch[q]), squarDist(ch[i+1], ch[q+1]))); } return ans; } int main() { while(scanf("%d", &n) != EOF) { if(n == 0) break; for(int i = 0; i < n; i++) scanf("%lf%lf", &p[i].x, &p[i].y); ConvexHull(); printf("%d\n", rotating_calipers()); } return 0; }
好早以前看的,现在再记下来吧,当做复习一遍。
那么,先提一下最基本最暴力的求凸包直径的方法吧---枚举。。。好吧。。很多问题都可以用 枚举 这个“万能”的方法来解决,过程很简单方便是肯定的,不过在效率上就要差很远了。 要求一个点集的直径,即使先计算出这个点集的凸包,然后再枚举凸包上的点对,这样来求点集直径的话依然会在凸包上点的数量达到O(n)级别是极大的降低它的效率,也浪费了凸包的优美性质。不过在数据量较小或者很适合时,何必要大费周折的用那些麻烦复杂的算法呢,枚举依然是解决问题的很好的方法之一。
然后就是今天的旋转卡壳算法了。(那个字念 qia 。。。搞了好久才发现读都读错了。囧。。。)
旋转卡壳可以用于求凸包的直径、宽度,两个不相交凸包间的最大距离和最小距离等。虽然算法的思想不难理解,但是实现起来真的很容易让人“卡壳”。
其实简单来说就是用一对平行线“卡”住凸包进行旋转。
被一对卡壳正好卡住的对应点对称为对踵点,对锺点的具体定义不好说,不过从图上还是比较好理解的。
可以证明对踵点的个数不超过3N/2个 也就是说对踵点的个数是O(N)的
对踵点的个数也是我们下面解决问题时间复杂度的保证。
卡壳呢,具体来说有两种情况:
1.
2.
而第二种情况在实现中比较容易处理,这里就只研究第二种情况。
在第二种情况中 我们可以看到 一个对踵点和对应边之间的距离比其他点要大(借用某大神的图··)
也就是一个对踵点和对应边所形成的三角形是最大的 下面我们会据此得到对踵点的简化求法。
下面给出一个官方的说明:
Compute the polygon's extreme points in the y direction. Call them ymin and ymax. Construct two horizontal lines of support through ymin and ymax. Since this is already an anti-podal pair, compute the distance, and keep as maximum. Rotate the lines until one is flush with an edge of the polygon. A new anti-podal pair is determined. Compute the new distance, compare to old maximum, and update if necessary. Repeat steps 3 and 4 until the anti-podal pair considered is (ymin,ymax) again. Output the pair(s) determining the maximum as the diameter pair(s).
要是真的按这个实现起来就麻烦到吐了。。
根据上面的第二种情况,我们可以得到下面的方法:
如果qa,qb是凸包上最远两点,必然可以分别过qa,qb画出一对平行线。通过旋转这对平行线,我们可以让它和凸包上的一条边重合,如图中蓝色直线,可以注意到,qa是凸包上离p和qb所在直线最远的点。于是我们的思路就是枚举凸包上的所有边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。
直观上这是一个O(n2)的算法,和直接枚举任意两个顶点一样了。
然而我们可以发现 凸包上的点依次与对应边产生的距离成单峰函数(如下图:)
这个性质就很重要啦。
根据这个凸包的特性,我们注意到逆时针枚举边的时候,最远点的变化也是逆时针的,这样就可以不用从头计算最远点,而可以紧接着上一次的最远点继续计算。于是我们得到了O(n)的算法。这就是所谓的“旋转”吧!
利用旋转卡壳,我们可以在O(n)的时间内得到凸包的对锺点中的长度最长的点对。
又由于最远点对必然属于对踵点对集合 ,那么我们先求出凸包 然后求出对踵点对集合 然后选出距离最大的即可
那么具体的代码就很容易实现了,利用叉积,代码只有这么几行的长度:
double rotating_calipers(Point *ch,int n) { int q=1; double ans=0; ch[n]=ch[0]; for(int p=0;p<n;p++) { while(cross(ch[p+1],ch[q+1],ch[p])>cross(ch[p+1],ch[q],ch[p])) q=(q+1)%n; ans=max(ans,max(dist(ch[p],ch[q]),dist(ch[p+1],ch[q+1]))); } return ans; }