POJ 2187 - Beauty Contest - [凸包+旋轉卡殼法][凸包的直徑]

題目連接:http://poj.org/problem?id=2187html

Time Limit: 3000MS Memory Limit: 65536Kc++

Description算法

Bessie, Farmer John's prize cow, has just won first place in a bovine beauty contest, earning the title 'Miss Cow World'. As a result, Bessie will make a tour of N (2 <= N <= 50,000) farms around the world in order to spread goodwill between farmers and their 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函數

* Line 1: A single integer, N 

* Lines 2..N+1: Two space-separated integers x and y specifying coordinate of each farm 

Output優化

* Line 1: A single integer that is the squared distance between the pair of farms that are farthest apart from each other. 

Sample Inputui

4
0 0
0 1
1 1
1 0

Sample Outputspa

2

Hint3d

Farm 1 (0, 0) and farm 3 (1, 1) have the longest distance (square root of 2) 

 

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

相關文章
相關標籤/搜索