題目連接: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$ 是凸包邊界上的點數。