1. 程式人生 > 實用技巧 >【BZOJ3707】圈地 (幾何,旋轉座標系)

【BZOJ3707】圈地 (幾何,旋轉座標系)

【BZOJ3707】圈地 (幾何,旋轉座標系)

Description

2維平面上有n個木樁,黃學長有一次圈地的機會並得到圈到的土地,為了體現他的高風亮節,他要使他圈到的土地面積儘量小。圈地需要圈一個至少3個點的多邊形,多邊形的頂點就是一個木樁,圈得的土地就是這個多邊形內部的土地。(因為黃學長非常的神,所以他允許圈出的第n點共線,那樣面積算0)

Input

第一行一個整數n,表示木樁個數。
接下來n行,每行2個整數表示一個木樁的座標,座標兩兩不同。

Output

僅一行,表示最小圈得的土地面積,保留2位小數。

Sample Input

3
0 0
0 1
1 0

Sample Output

0.50

題解:

如果我們確定了2個點以後,第三個點有必要去盲目的列舉嗎?答案是否定的。實際上我們把經過這兩點的線看成一個斜率,把他當成y軸你會發現第三個點明顯是在座標系左右找一個離”y軸”最近的點來算面積更新答案。然後我們可以繼續思考,發現我們可以把點按照某個斜率當成”y軸”進行“從左到右”的排序,這樣當2點共線的時候,用這兩個點的左右2個點去更新答案就好了。也就是說我們採用旋轉座標系的方法,一開始按x座標排好序,認為直接用豎著的那條斜率,然後維護的話每次其實當兩點共線後只要交換他們就能得到斜率轉過該事件點的序列。所以我們可以預處理出所有可行的斜率,當成事件點,不斷轉動座標系更新答案就好。這樣複雜度只有n^2。

程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<ctime>
#define inf 1000000000
#define ll long long
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,block,cnt;
double ans=1e60;
struct P{double x,y;}p[1005];
P operator -(P a,P b){return (P){a.x-b.x,a.y-b.y};}
double cross(P a,P b){return a.x*b.y-a.y*b.x;}
bool operator <(P a,P b)
{
	return a.x<b.x||(a.x==b.x&&a.y<b.y);
}
P rotate(P a,double x)
{
	return (P){a.x*cos(x)-a.y*sin(x),a.y*cos(x)+a.x*sin(x)};
}
void cal(int a,int b)
{
	for(int i=a;i<=b-2;i++)
		for(int j=i+1;j<=b-1;j++)
			for(int k=j+1;k<=b;k++)
			    ans=min(ans,abs(cross(p[i]-p[k],p[j]-p[k]))/2);
}
void solve()
{
	double t=rand()/10000;
    for(int i=1;i<=n;i++)
		p[i]=rotate(p[i],t);
	sort(p+1,p+n+1);
	for(int i=1;i<=cnt;i++)
	{
		int t1=block*(i-1)+1,t2=block*i;
		t2=min(t2,n);
		cal(t1,t2);
	}
}
int main()
{
	//freopen("land.in","r",stdin);
	//freopen("land.out","w",stdout);
	n=read();srand(time(0));
	block=sqrt(n)+10;
	cnt=n/block+(n%block!=0);
	for(int i=1;i<=n;i++)
		p[i].x=read(),p[i].y=read();
	for(int i=1;i<=50;i++)
		solve();
	printf("%.2lf",ans);
	return 0;
}