题目连接:http://poj.org/problem?id=2187html
Time Limit: 3000MS Memory Limit: 65536Kc++
Description算法
Input函数
Output优化
Sample Inputui
4 0 0 0 1 1 1 1 0
Sample Outputspa
2
Hint3d
题意:code
有一头美牛,她要再一个二维平面上,跑遍上面全部的农场(每一个农场都有一个坐标,而且农场不重合),她如今想知道任意两个农场中,最大距离的平方是多少?htm
题解:
一看农场有几座,50000……emmm,看来 $O(n^2)$ 的暴力是确定跪了的。而后不难发现,两农场间最大距离确定是出如今凸包边界上的两个点间;
因此咱们求出凸包,遍历凸包上的点能够优化时间复杂度。
因为这题时间卡的不紧,那要是数据里面有一个 $50000$ 个点的凸包边界呢?同样要GG。
因此,咱们有了一种新的方法叫作旋转卡壳法,具体什么个方法,网上有一张很常见的图:
参考关于旋转卡壳的讲解:https://www.cnblogs.com/xdruid/archive/2012/07/01/2572303.html
卡壳的一种状况是这样,两边分别卡着凸包的一条边和一个点。(另外一种是同时卡住两个点,这两个点被称为对踵点)
这种状况在实现中比较容易处理,这里就只研究这种状况。
在第二种状况中 咱们能够看到一个对踵点和对应边之间的距离比其余点要大。
也就是说,一个对踵点和对应边所造成的三角形面积是最大的,据此能够获得对踵点的简化求法。
若是 $q_a,q_b$ 分别是凸包上最远的两点,必然能够分别过 $q_a,q_b$ 画出一对平行线(即卡壳)。
而后经过旋转这对平行线,咱们可让它和凸包上的一条边重合,即图中的直线 $(q_a,p)$,能够注意到,此时 $q_a$ 是凸包上离直线 $(q_a,p)$ 最远的点。
因而咱们的思路就是:枚举凸包上的全部边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。
直观上这是一个 $O(n^2)$ 的算法,和直接枚举任意两个顶点同样。
然而咱们能够发现,凸包上的点依次与对应边产生的距离成单峰函数:
根据这个凸包的特性,能够注意到,逆时针枚举边的时候,最远点的变化也是逆时针的,这样就能够不用从头计算最远点,而能够紧接着上一次的最远点继续计算。
因而咱们获得了 $O(n)$ 的求最远点对的算法:利用旋转卡壳,咱们能够在 $O(n)$ 的时间内获得凸包的对踵点中长度最长的点对;又因为最远点对必然属于对踵点对集合,因此用旋转卡壳求出对踵点对集合,而后维护对踵点间最大的距离便可。
代码模板:
double RotatingCalipers(const vector<Point> &ch) //旋转卡壳法 { double ans=0; int sz=ch.size(); for(int i=0,q=1;i<sz;i++) { int j=(i+1)%sz; while( Cross(ch[j]-ch[i],ch[(q+1)%sz]-ch[i]) > Cross(ch[j]-ch[i],ch[q]-ch[i]) ) q=(q+1)%sz; ans=max( ans, max(Length(ch[i],ch[q]),Length(ch[j],ch[q])) ); ans=max( ans, max(Length(ch[i],ch[(q+1)%sz]),Length(ch[j],ch[(q+1)%sz])) ); } return ans; }
其中这么维护最大值的缘由是考虑这种状况:
AC代码(在OpenJudge百练2187提交):
#include<bits/stdc++.h> #define mk make_pair #define fi first #define se second #define pb push_back using namespace std; const double eps=1e-8; const double INF=1e18; int Sign(double x) { if(x<-eps) return -1; if(x>eps) return 1; return 0; } int Cmp(double x,double y){return Sign(x-y);} struct Point { double x,y; Point(double _x=0,double _y=0):x(_x),y(_y){} Point operator+(const Point &o)const{return Point(x+o.x,y+o.y);} Point operator-(const Point &o)const{return Point(x-o.x,y-o.y);} Point operator*(double k)const{return Point(x*k,y*k);} Point operator/(double k)const{return Point(x/k,y/k);} int operator==(const Point &o)const{return Cmp(x,o.x)==0 && Cmp(y,o.y)==0;} bool operator<(const Point &o)const { int sgn=Cmp(x,o.x); if(sgn==-1) return 1; else if(sgn==1) return 0; else return Cmp(y,o.y)==-1; } void print(){printf("%.11f %.11f\n",x,y);} }; typedef Point Vctor; //叉积 double Cross(Vctor A,Vctor B){return A.x*B.y-A.y*B.x;} double Cross(Point O,Point A,Point B){return Cross(A-O,B-O);} //距离 double Dot(Vctor A,Vctor B){return A.x*B.x+A.y*B.y;} double Length(Vctor A){return sqrt(Dot(A,A));} double Length(Point A,Point B){return Length(A-B);} vector<Point> ConvexHull(vector<Point> P,int flag=1) //flag=0不严格 flag=1严格 { if(P.size()<=1) return P; int sz=P.size(); vector<Point> ans(2*sz); sort(P.begin(),P.end()); int now=-1; for(int i=0;i<sz;i++) { while(now>0 && Sign(Cross(ans[now]-ans[now-1],P[i]-ans[now-1]))<flag) now--; ans[++now]=P[i]; } int pre=now; for(int i=sz-2;i>=0;i--) { while(now>pre && Sign(Cross(ans[now]-ans[now-1],P[i]-ans[now-1]))<flag) now--; ans[++now]=P[i]; } ans.resize(now); return ans; } double RotatingCalipers(const vector<Point> &P) //旋转卡壳法 { double ans=0; int sz=P.size(); for(int i=0,q=1;i<sz;i++) { int j=(i+1)%sz; while( Cross(P[j]-P[i],P[q]-P[i]) < Cross(P[j]-P[i],P[(q+1)%sz]-P[i]) ) q=(q+1)%sz; ans=max( ans, max(Length(P[i],P[q]),Length(P[j],P[q])) ); ans=max( ans, max(Length(P[i],P[(q+1)%sz]),Length(P[j],P[(q+1)%sz])) ); } return ans; } int n; vector<Point> P; int main() { cin>>n; for(int i=1;i<=n;i++) { double x,y; cin>>x>>y; P.pb(Point(x,y)); } double ans=RotatingCalipers(ConvexHull(P)); printf("%.0f\n",ans*ans); }
PS.用jls给的几何板子更新了一下模板,这里求凸包的方法用的是Jarris步进法,时间复杂度 $O(nH)$,其中 $H$ 是凸包边界上的点数。