POJ 2187 Beauty Contest(旋轉卡殼)
Beauty Contest
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 Input
4
0 0
0 1
1 1
1 0
Sample Output
2
【思路分析】
求一個平面內最遠點對距離的平方,用到的演算法是旋轉卡(qia3)殼(qiao4)。
以下是求解的大致流程:
1、找出平面內的凸包。
最遠點對一定在凸包上(可以反證法證之),所以找出凸包後在其上找最遠點對就可以少考慮很多點。這題資料比較弱,貌似凸包上面直接暴力列舉就能AC。但是一般情況下,直接暴力列舉最壞時間複雜度為O(n * n),即所有點都在凸包上,這時就需要進一步優化了。
求凸包我用的是andrew演算法(詳見大白書),它是基於graham演算法,且更快更穩定。不同於graham演算法的逆時針排序,andrew演算法採用了點的水平排序,然後分別求下凸包以及上凸包,這樣下來整個的凸包便求好了。而且該演算法可以解決凸包中有重合點、共線點等問題(目前還沒有完全明白為什麼)。下圖很好的演示了andrew演算法的過程。
2、旋轉卡殼
聽起來很NB,其實就是兩個平行線正好把凸包卡住,其中卡著的點稱為對踵點對,見下圖。
很容易想象,某兩個平行線對應的對踵點對最多隻有四個。
那麼怎麼才能找到最遠點對呢?首先見下圖。
對於上圖中的6個三角形的公共底邊,可知當三角形的頂點在凸包上按逆時針旋轉時,三角形的面積先由小變大再由大變小,即成單峰函式變化。所以在峰值時對應的凸包頂點距底邊對應的兩個對踵點的距離比其他情況大,這樣,列舉每一個邊並找到其面積單峰函式峰值對應的頂點,同時更新最大距離,旋轉一趟下來便可以得到最後的結果。求面積單峰函式的峰值也很簡單,就是通過叉積比較兩個三角形的大小,當後者的面積比前者小時便找到了峰值,然後求距離更新最大值即可。
旋轉平行線時就可以按照上述的思路進行了。(PS:上面盜了三張圖,不知道主人是誰,歡迎認領)。
程式碼如下:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 50005;
int top,n,start;
int stacks[maxn];//記錄凸包中的點
struct Point
{
int x,y;
}points[maxn];
int maxs(int a,int b)
{
return a > b ? a : b;
}
int distances(Point a,Point b)
{
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
int cross(Point p0,Point p1,Point p2)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
bool cmp(Point p1,Point p2)//橫座標從小到大排序
{
if(p1.x != p2.x)
{
return p1.x < p2.x;
}
else
{
return p1.y < p2.y;
}
}
void andrew(int n)//找凸包O(nlogn)
{
sort(points,points + n,cmp);
for(int i = 0;i < n;i++)//下凸包
{
while(top >= 2 && cross(points[stacks[top - 2]],points[stacks[top - 1]],points[i]) <= 0)
{
top--;
}
stacks[top++] = i;//新元素壓棧
}
int temp = top + 1;
for(int i = n - 2;i >= 0;i--)//上凸包
{
while(top >= temp && cross(points[stacks[top - 2]],points[stacks[top - 1]],points[i]) <= 0)
{
top--;
}
stacks[top++] = i;
}
if(n > 1)
top--;//因為起點被計算了兩次
}
int rotatingCaliper()//旋轉卡殼O(n)
{
int q = 1;
int ans = 0;
for(int i = 0;i < top;i++)//凸包上的每一個點
{
while(cross(points[stacks[i]],points[stacks[i + 1]],points[stacks[q + 1]]) >
cross(points[stacks[i]],points[stacks[i + 1]],points[stacks[q]]))
{
q = (q + 1) % top;//單峰函式,直到最高點時跳出迴圈
}
int d1 = distances(points[stacks[i]],points[stacks[q]]);
int d2 = distances(points[stacks[i + 1]],points[stacks[q]]);
ans = maxs(ans,maxs(d1,d2));//更新最大值
}
return ans;
}
void init()
{
top = 0;
for(int i = 0;i < n;i++)
{
scanf("%d %d",&points[i].x,&points[i].y);
}
}
void solve()
{
andrew(n);
int res = rotatingCaliper();
printf("%d\n",res);
}
int main()
{
while(scanf("%d",&n) != EOF)
{
init();
solve();
}
return 0;
}