JZOJ4033. 【GCJ2009B】Min Perimeter

Description

给你一个整数坐标的点集,询问点集中最小的三角形周长是多少。退化的三角形也是允许的(面积为0)。

Input

第一行一个整数n表示共有n个点
接下来n行每行两个整数xi,yi表示点的坐标
保证没有重复点

Output

仅一行包含一个实数表示最小的三角形周长,输出和标准答案相差10^-9之内都被认为是正确的。

Sample Input

4
0 0
2 0
0 2
2 2

Sample Output

6.828427124746

Data Constraint

0 < n<=100000

题解

首先先看最最暴力的方法, O(n3) O ( n 3 ) 的暴力匹配。
很显然这是不可行的。

而像这种题目一般就是用分治来做,
按照x从小到大排序,
每次选择最中间,建点集分成大小相同的两个部分。
现在就只需要考虑如何求出跨过中间线的三角形。
不过在此之前,就可以通过分治左右两边的点集得到一个最小值,
而通过这个最小值,对不少点进行剪枝。
假设当前的最小值为p,
你们肯能成为新的最小值的三个点只可能分布在一个边长为p,p/2的长方形中,
也就是说中线往左右各p/2里面的所有点。
于是就先将这些点拿出来按照y排序,
然后用过队列维护,
每次加入一个点,然后跟队列里面所有的点暴力配对。
最后再将一些不可能成为更新答案的点踢掉。

code

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#include <time.h>
#define ll long long
#define N 100003
#define M 103
#define db double
#define P putchar
#define G getchar
#define inf 998244353
#define pi 3.1415926535897932384626433832795
using namespace std;
char ch;
void read(ll &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    ll w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
ll abs(ll x){return x<0?-x:x;}
ll sqr(ll x){return x*x;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}

struct node
{
    ll x,y;
}a[N],b[N];

db dis(node a,node b)
{
    return sqrt((db)(a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

db get(node a,node b,node c)
{
    return dis(a,b)+dis(a,c)+dis(b,c);
}

bool cmp(node a,node b)
{
    return a.x<b.x;
}

bool cmp1(node a,node b)
{
    return a.y<b.y;
}

ll n;

db solve(int l,int r)
{
    int m=(l+r)>>1;
    db mi=2147483647;
    if(r-l<2)return mi;
    mi=min(solve(l,m),solve(m+1,r));

    db t=mi/2,w=a[m].x;
    int st,fi;

    for(st=m;st>=l && w-a[st].x<=t;st--);
    for(fi=m;fi<=r && a[fi].x-w<=t;fi++);
    for(int i=st+1;i<fi;i++)b[i-st]=a[i];
    int p=fi-st-1;

    sort(b+1,b+1+p,cmp1);

    int id=1;
    for(int i=1;i<=p;i++)
    {
        for(int j=id;j<i;j++)
            for(int k=j+1;k<i;k++)
                mi=min(mi,get(b[i],b[j],b[k]));
        for(t=mi/2;b[i].y-b[id].y>t && id<i;id++);
    }

    return mi;  
}

int main()
{
    read(n);
    for(int i=1;i<=n;i++)
        read(a[i].x),read(a[i].y);

    sort(a+1,a+1+n,cmp);

    printf("%.10lf",solve(1,n));

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值