1. 程式人生 > >【計算幾何+巧妙旋轉】 bzoj 3707

【計算幾何+巧妙旋轉】 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);
}