POJ 2187 Beauty Contest【旋转卡壳求凸包直径】
链接:
Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 24254 | Accepted: 7403 |
Description
cows. For simplicity, the world will be represented as a two-dimensional plane, where each farm is located at a pair of integer coordinates (x,y), each having a value in the range -10,000 ... 10,000. No two farms share the same pair of coordinates.
Even though Bessie travels directly in a straight line between pairs of farms, the distance between some farms can be quite large, so she wants to bring a suitcase full of hay with her so she has enough food to eat on each leg of her journey. Since Bessie refills
her suitcase at every farm she visits, she wants to determine the maximum possible distance she might need to travel so she knows the size of suitcase she must bring.Help Bessie by computing the maximum distance among all pairs of farms.
Input
* Lines 2..N+1: Two space-separated integers x and y specifying coordinate of each farm
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;/**返回的也就是凸包的直径*/
}
思路:
code1:暴力+凸包【对应于凸包上点比较少】
/********************************************
题意:给你 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;
}
code2:凸包+旋转卡壳【对于凸包上点比较多】
/********************************************
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;
}
其中cross()是计算叉积,因为凸包上距离一条边最远的点和这条边的两个端点构成的三角形面积是最大的。之所以既要更新(ch[p],ch[q])又要更新(ch[p+1],ch[q+1])是为了处理凸包上两条边平行的特殊情况。
POJ 2187 Beauty Contest【旋转卡壳求凸包直径】的更多相关文章
- poj 2187 Beauty Contest , 旋转卡壳求凸包的直径的平方
旋转卡壳求凸包的直径的平方 板子题 #include<cstdio> #include<vector> #include<cmath> #include<al ...
- poj 2187:Beauty Contest(旋转卡壳)
Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 32708 Accepted: 10156 Description Bes ...
- poj 2187 Beauty Contest——旋转卡壳
题目:http://poj.org/problem?id=2187 学习材料:https://blog.csdn.net/wang_heng199/article/details/74477738 h ...
- poj 2187 Beauty Contest —— 旋转卡壳
题目:http://poj.org/problem?id=2187 学习资料:https://blog.csdn.net/wang_heng199/article/details/74477738 h ...
- UVa 1453 - Squares 旋转卡壳求凸包直径
旋转卡壳求凸包直径. 参考:http://www.cppblog.com/staryjy/archive/2010/09/25/101412.html #include <cstdio> ...
- poj 2187 Beauty Contest(凸包求解多节点的之间的最大距离)
/* poj 2187 Beauty Contest 凸包:寻找每两点之间距离的最大值 这个最大值一定是在凸包的边缘上的! 求凸包的算法: Andrew算法! */ #include<iostr ...
- POJ 2187 - Beauty Contest - [凸包+旋转卡壳法][凸包的直径]
题目链接:http://poj.org/problem?id=2187 Time Limit: 3000MS Memory Limit: 65536K Description Bessie, Farm ...
- POJ 2187 Beauty Contest(凸包,旋转卡壳)
题面 Bessie, Farmer John's prize cow, has just won first place in a bovine beauty contest, earning the ...
- POJ 2187 Beauty Contest(凸包+旋转卡壳)
Description Bessie, Farmer John's prize cow, has just won first place in a bovine beauty contest, ea ...
随机推荐
- Java学习之自动装箱和自动拆箱源码分析
自动装箱(boxing)和自动拆箱(unboxing) 首先了解下Java的四类八种基本数据类型 基本类型 占用空间(Byte) 表示范围 包装器类型 boolean 1/8 true|false ...
- ASP.NET MVC学习---(七)Razor视图引擎语法
之前体验过razor视图引擎@符号的威力了吧~ 连我这个初学者都能感觉到确实省时省力方便快捷 简直就是居家旅行*人*货必备之物啊(这里和谐两个字~) 那么现在就开始对razor的语法进一步介绍~ 1. ...
- java读取properties文件,并在配置文件中设置默认浏览器驱动
java中的properties文件是一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式,在properti ...
- iOS_block代码块
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcHJlX2VtaW5lbnQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...
- JDBC性能优化方案
最近用到了利用JDBC查询Oracle数据库.可是查询效率不尽人意.研究了一下JDBC方面能够优化的地方,在这里跟大家分享一下. 1.设置最优的预取值 defaultRowP ...
- java web 分页实现
分页实现的效果: ///////// /////////////////////////////////////////////////////// /////////////////// ...
- Volatile 多线程中用到的关键字
前言 不管是在面试还是实际开发中 volatile 都是一个应该掌握的技能. 首先来看看为什么会出现这个关键字. 内存可见性 由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个 ...
- MySQL - Show Processlist 整理(转)
原文来源:MySQL 5.5 Reference Manual 部分翻译取自:<MySQL_5.1中文参考手册> 转载请注明原文链接http://www.cnblogs.com/len ...
- javascript中keyCode与charCode属性
好记性不如烂笔头啊,最近总是忘记这两个属性的区别.想了想,从别人博客上转一遍过来吧,时常看下 键盘事件拥有两个属性,keyCode和CharCode,他们之间有一些不一样之处.keyCode表示用户按 ...
- Java编程手冊-Collection框架(上)
该文章所讲内容基本涵盖了Collection里面的全部东西,尽管基于jdk 1.5的.可是思路非常清晰 1.引言 1.1 Collection框架的介绍 尽管我们能够使用数组去存储具有同样类型的元素集 ...