codeforces 1019D Large Triangle

题意:
给出n个点和一个面积s,问能否从n个点中选3个点组成s的三角形的面积为s。

题解:

这题我思考了很久,因为确实方法不是很好懂。

首先,原题是bzoj 3707,这道题是给出n个点,求3个点组成的最小面积。

我们先从这题入手。

解法一:分块+暴力

            将坐标系多次旋转一个随机角度,将点排序后,由于是求最小值,所以在相邻的点最有可能,可以分块,在块内部暴力求解。

解法二:枚举线段+O(1)找最小值

            先将点以x轴坐标为关键字排序,再枚举所有的线段,将线段以斜率为关键字排序。

两个数组:

            rk[i]表示第i个点的序号。

            pos[i]表示序号为i的点是第几个点。

当我们枚举第一条线段时,他的斜率一定是最小的,而且它的两个端点的序号一定是紧挨着的。

那么,与这条线段形成的面积最小的三角形的点一定满足下列性质:

将线段当做y轴旋转坐标系后,横坐标距离线段最近的点就是面积最小的点。

而rk数组和pos数组,维护的就是一个序列,这个序列满足,序号从小到大,到当前的斜率的直线的距离满足距离递减,可以想象x轴的无穷远处有1条斜率已知的直线。

那么一开始,这个序列就是1-n,因为我们先按x坐标排序了,符合条件。

当判断完第一条线段,要判断第二条线段时,显然这个序列不再满足条件,需要做一些调整。

设第一条线段的两个点为u、v,当判断第二条线段时,画图可以发现,只有u、v两点不满足条件,而且调整方法就是交换u、v两点的序号。

这样,我们知道线段的端点u、v的序号rk[u]、rk[v](rk[u]<rk[v]),那么,序号为rk[u]-1或者rk[v]+1的点与线段形成的面积最小。

这时,pos数组就派上用场,因为刚刚说了需要一个序号到点的映射,可见,pos和rk是互逆的。

回到CF这道题来,根据序列的性质,序号的单调与面积的单调是一致的,所以我们可以用二分查找。

代码:

#include<bits/stdc++.h>
#define N 2001
#define INF 0x3f3f3f3f
#define eps 1e-10
#define pi 3.141592653589793
#define LL long long
#define pb push_back
#define cl clear
#define si size
#define lb lowwer_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

struct Point
{
    LL x,y;
    Point(LL x=0,LL y=0):x(x),y(y){}
    bool operator < (const Point z) const
    {
        return x<z.x || (x==z.x && y<z.y);
    }
    Point operator - (const Point z) const
    {
        return Point(x-z.x,y-z.y);
    }
}a[N];
LL Cross(Point z,Point zz)
{
    return z.x*zz.y-z.y*zz.x;
}
struct Seg
{
    int u,v;
    Point p;
}seg[N*N/2];

bool cmp (Seg z,Seg zz)
{
    return Cross(z.p,zz.p)>0;
}

int rk[N],pos[N];

int main()
{
    int n;LL s;
    sc(n); scanf("%lld",&s); s<<=1;
    for (int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y);
    sort(a+1,a+n+1);
    int cnt=0;
    for (int i=1;i<=n;i++)
        for (int j=i+1;j<=n;j++) seg[++cnt]=Seg{i,j,a[j]-a[i]};
    sort(seg+1,seg+cnt+1,cmp);
    for (int i=1;i<=n;i++) rk[i]=pos[i]=i;
    for (int i=1;i<=cnt;i++)
    {
        Point p=seg[i].p;
        int u=seg[i].u,v=seg[i].v,l,r;
        if (rk[u]>rk[v]) swap(u,v);
        l=1;    r=rk[u]-1;
        while(l<=r)
        {
            int t=(l+r)>>1;
            LL tm=abs(Cross(p,a[pos[t]]-a[pos[rk[u]]]));
            if (tm==s)
            {
                cout<<"Yes\n"<<a[u].x<<' '<<a[u].y<<"\n"<<a[v].x<<' '<<a[v].y<<"\n"<<a[pos[t]].x<<' '<<a[pos[t]].y;
                return 0;
            }else tm>s?l=t+1:r=t-1;
        }
        swap(rk[u],rk[v]);
        swap(pos[rk[u]],pos[rk[v]]);
    }
    cout<<"No";
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值