【計算幾何+巧妙旋轉】 bzoj 3707
【題目大意】給定n個點,求這n個點圍出來的多邊形【邊數>=3】的最小面積。【如果有三點共線,面積可以為0】
【思路】如果圍成的形狀多於3邊,我們一定可以把它分成若干個三角形。所以如果要最小就必須是三角形。
我先考慮暴力做法:分別列舉3個頂點,O(n^3)。【10分】
然鵝我再思考一下:如果確定了三角形的一條邊,我們可以將整個座標系旋轉一下,使這條邊成為新的y軸,這時候我們只要找到離這個“y軸”最近的一個點就行了。
【到這我就不會了】
到網上搜一搜,我們發現:可以先預處理一下每兩個點之間的直線,記錄一下起點終點和斜率。
首先所有點按x從小到大排序。然後直線按斜率從小到大排個序。
當我們處理到直線AB時,AB就成為了y軸,且A,B是相鄰的(它們距離y軸距離都為0)(假設A位置在B的上面),離AB最近的點就是AB兩側最近的點。然後到下一條直線時,由於斜率從小到大,所以是把整個圖順時針旋轉了一點點,直到下一條直線成為y軸。那麼這個時候把A,B在序列中的位置交換一下就行了。因為如果有別的點對相對順序改變,那麼這個點對的斜率一定介於這兩條直線之間。下面口胡一下。
【這裡的排位是指:在當前的“y軸”下,橫座標從小到大排序所對應的排名】
假設現在我們以l1為y軸。有一組點對,P和Q,它們相對於l1的橫座標分別是a和b,其中a<b。
那麼我們一定可以保證PQ的斜率比l1小,因為l1要逆時針旋轉一點點才與PQ平行。【相對原座標系】
然後我們看到比l1斜率更小的第一條直線l2。首先,可以保證,A和B一定在直線l2的同側。因為如果A和B在l2的異側,那ABCD這四個點一定可以生成一條斜率介於l1和l2之間的直線【畫一畫就知道了】,而我們是把斜率排了序,保證了不會有這種情況出現。現在,將整個圖再順時針旋轉一點點,使l2成為新的y軸。那麼可以保證,A和B的相對順序一定是改變了的,而且是把A和B的排位交換了一下。【可以腦補一下AB在CD下側的情況,是同理的】這時候,我們看到PQ,在新的y軸下,P的橫座標是c,Q的橫座標是d,假設c>d,那麼PQ的斜率一定比l2的斜率要大,因為PQ逆時針旋轉一點點就與l2平行。
對於旋轉過後排位會變化的點對【除AB外】,一定會滿足PQ的條件:a<b且c>d。但是我們發現它的斜率介於l1和l2之間,然鵝比l1斜率更小的第一條直線是l2,不是PQ,與前提矛盾,所以不存在這樣的PQ。
那麼就可以保證從l1旋轉到l2時,受影響的就只有l1上的兩個點。
#include<bits/stdc++.h>
using namespace std;
const double pi=3.14159265358979;
const int maxn=1005;
const int M=1000010;
struct point{
double x,y;
point(double _x=0,double _y=0){x=_x,y=_y;}
friend inline point operator +(const point &a,const point &b){
return point(a.x+b.x,a.y+b.y);
}
friend inline point operator -(const point &a,const point &b){
return point(a.x-b.x,a.y-b.y);
}
friend inline double operator *(const point &a,const point &b){
return (a.x*b.y-a.y*b.x);
}
}p[maxn];
struct line{
int s,t;
double k;
}b[M];
int n,i,j,tot=0;
double ans=1e18;
int id[maxn],pos[maxn];
bool cmp(point a,point b){
if(a.x==b.x) return a.y<b.y;
return a.x<b.x;
}
bool Cmp(line a,line b){
return a.k<b.k;
}
double area(point u,line v){
return fabs((p[v.s]-u)*(p[v.t]-u))*0.5;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%lf%lf",&p[i].x,&p[i].y);
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n;++i) id[i]=pos[i]=i;
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
++tot;
b[tot].s=i,b[tot].t=j;
if(p[i].x==p[j].x) b[tot].k=1e18;
else b[tot].k=(p[i].y-p[j].y)/(p[i].x-p[j].x);
}
}
sort(b+1,b+tot+1,Cmp);
//pos[i]存的是p[i]的排名
//id[i]存的是 排名為i的點
for(int i=1;i<=tot;++i){
int j=pos[b[i].s],k=pos[b[i].t];
if(j>k) swap(j,k);
if(j>1) ans=min(ans,area(p[id[j-1]],b[i]));
if(k<n) ans=min(ans,area(p[id[k+1]],b[i]));
if(ans==0) break;
swap(pos[b[i].s],pos[b[i].t]);
swap(id[j],id[k]);
}
printf("%0.2f\n",ans);
}