[BZOJ 3707] 圈地

Link:

BZOJ 3707 传送门

Solution:

很不错的一道思维题

可以发现枚举完两点后最优点就是最接近该直线的点

如果将该直线看作$y$轴用两边$x$坐标的绝对值最小的点更新即可

 

于是可以将所有斜率排序后不断旋转$y$轴并维护当前按$x$从小到大的序列

发现对于直线$(x,y)$,两点$x$坐标的相对关系仅在$y$轴斜率超过直线斜率后才会变化

因此每处理完一条直线,将两点的顺序交换即可!

 

注意:这题BZOJ好像有锅,本身就垂直于$y$轴的直线必须最后处理

Code:

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
#define pb push_back
typedef double db;
typedef pair<db,db> P;
typedef long long ll;
const int MAXN=1e3+10;
db res=1e60,INF=1e60;P dat[MAXN];
int n,seq[MAXN],mp[MAXN],tot;

P operator - (P a,P b)
{return P(a.X-b.X,a.Y-b.Y);}
inline db Cross(P a,P b)
{return a.X*b.Y-a.Y*b.X;}
inline db Slope(P a,P b)
{return a.X!=b.X?(a.Y-b.Y)/(a.X-b.X):INF;}
struct Line{double x,y,slope;}line[MAXN*MAXN];
bool cmp(Line a,Line b){return a.slope<b.slope;}
inline db solve(int a,int b,int c)
{return fabs(Cross(dat[b]-dat[a],dat[c]-dat[a]))/2.0;}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lf%lf",&dat[i].X,&dat[i].Y);
    sort(dat+1,dat+n+1);
    for(int i=1;i<=n;i++) 
        seq[i]=mp[i]=i;
    for(int i=1;i<n;i++)
        for(int j=i+1;j<=n;j++)
    //注意与y轴平行的线要放在最后处理(数据的锅?)
            line[++tot]=(Line){i,j,Slope(dat[i],dat[j])};
    sort(line+1,line+tot+1,cmp);
    //将y轴不断旋转并维护当前从左向右的序列 
    for(int i=1;i<=tot;i++)
    {
        int a=line[i].x,b=line[i].y;
        if(seq[a]>seq[b]) swap(a,b);
        if(seq[a]>1) res=min(res,solve(a,b,mp[seq[a]-1]));
        if(seq[b]<n) res=min(res,solve(a,b,mp[seq[b]+1]));
        swap(seq[a],seq[b]);
        swap(mp[seq[a]],mp[seq[b]]);
    }
    printf("%.2lf",res);
    return 0;
}

 

posted @ 2018-09-28 13:25  NewErA  阅读(142)  评论(0编辑  收藏  举报