bzoj2732: [HNOI2012]射箭

本文介绍了一种针对半平面交算法的时间复杂度优化方法。通过对半平面进行预排序,避免了重复排序操作,实现了O(Nlog_2N)的时间复杂度。文章提供了详细的算法解析及C++实现代码。

题目

http://www.lydsy.com/JudgeOnline/problem.php?id=2732

题解

  根据题意设出方程 y=ax2+bx(a<0,b>0) ,那么对于一条线段,通过它的条件就是
   {  ax2+bxy1  ax2+bxy2  
  变形
   {  by1xax  by2xax  
  建立关于 a b的平面直角坐标系
  就是看最多前多少个方程能被满足,很显然可行性是满足单调性的,所以二分答案,然后 O(log2N) 来判断是否可行。可行就是半平面交之后队列里至少有两个元素。
  这样的话,总的时间复杂度是 O(N(log2N)2) ,OJ上会T。
  考虑半平面交算法,我记得书上有句话“算法的瓶颈在于排序”,那么我们发现我们进行了很多重复的排序,可以直接在最开始先排好,每次扫描的时候只把“原来的序号<=二分的答案”这样的半平面进行半平面交就行了。
  复杂度 O(Nlog2N)

代码

//半平面交
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 300000
#define inf 1e10
using namespace std;
struct point{double x,y;};
struct vec
{
    point a, b;
    double th;
    int id;
    double operator*(vec v)
    {
        double x1=b.x-a.x, y1=b.y-a.y, x2=v.b.x-v.a.x, y2=v.b.y-v.a.y;
        return x1*y2-x2*y1;
    }
    void calcth(){th=atan2(b.y-a.y,b.x-a.x);}
}seg[maxn], q[maxn];
int N, cnt;
bool in(point p, vec v)
{return (vec){v.a,p}*v<0;}
bool cmp(vec v1, vec v2)
{return v1.th==v2.th?in(v1.a,v2):v1.th<v2.th;}
point cross(vec v1, vec v2)
{
    double k1, k2, b1, b2, x, y;
    k1=(v1.b.y-v1.a.y)/(v1.b.x-v1.a.x);
    k2=(v2.b.y-v2.a.y)/(v2.b.x-v2.a.x);
    b1=-k1*v1.a.x+v1.a.y;
    b2=-k2*v2.a.x+v2.a.y;
    if(v1.b.x==v1.a.x)x=v1.a.x,y=k2*x+b2;
    else if(v2.b.x==v2.a.x)x=v2.a.x,y=k1*x+b1;
    else x=(b2-b1)/(k1-k2),y=k1*x+b1;
    return (point){x,y};
}
void input()
{
    int i, j;
    double x, y1, y2;
    scanf("%d",&N);
    for(i=2;i<=N+1;i++)
    {
        scanf("%lf%lf%lf",&x,&y1,&y2);
        seg[i<<1]=(vec){(point){1,y1/x-x},(point){2,y1/x-2*x}};
        seg[(i<<1)+1]=(vec){(point){2,y2/x-2*x},(point){1,y2/x-x}};
        seg[i<<1].id=seg[(i<<1)+1].id=i-1;
    }
    seg[0]=(vec){(point){0,0},(point){0,inf}};
    seg[1]=(vec){(point){0,inf},(point){-inf,inf}};
    seg[2]=(vec){(point){-inf,inf},(point){-inf,0}};
    seg[3]=(vec){(point){-inf,0},(point){0,0}};
    for(i=0;i<=(N+1<<1)+1;i++)seg[i].calcth();
}
bool hpi(int n)
{
    if(n==0)return true;
    int i, l, r;
    l=0,r=-1;
    for(i=0;i<=cnt and l>=r;i++)if(seg[i].id<=n)q[++r]=seg[i];
    for(i++;i<=cnt;i++)
    {
        if(seg[i].id>n)continue;
        while(l<r and !in(cross(q[r],q[r-1]),seg[i]))r--;
        while(l<r and !in(cross(q[l],q[l+1]),seg[i]))l++;
        q[++r]=seg[i];
    }
    while(l<r and !in(cross(q[r],q[r-1]),q[l]))r--;
    while(l<r and !in(cross(q[l],q[l+1]),q[r]))l++;
    return l<r;
}
int bins()
{
    int l, r, mid, i;
    sort(seg,seg+(N+1<<1)+2,cmp);
    for(i=1;i<=(N+1<<1)+1;i++)if(seg[i].th!=seg[i-1].th)seg[++cnt]=seg[i];
    l=0,r=N,mid=l+r+1>>1;
    while(l<r)
    {
        if(hpi(mid))l=mid;
        else r=mid-1;
        mid=l+r+1>>1;
    }
    return l;
}
int main()
{
    input();
    printf("%d\n",bins());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值