[BZOJ 2732][HNOI 2012]射箭(半平面交)

142 篇文章 0 订阅
98 篇文章 0 订阅

题目链接

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

思路

对于每一关而言,它都对应了两个一元二次不等式,对于前 n 关就需要满足2n个不等式。这些不等式都是线性的,画在坐标系上也是直线,类似于线性规划,我们把最终抛物线方程 y=ax2+bx 中的参数 a 看作横坐标,参数b看成纵坐标,若前 k 关均可以满足,那么这2k个有向直线所构成的半平面交不为空,然后二分这个 k <script id="MathJax-Element-1072" type="math/tex">k</script>就可以了。
坑爹的是今天BZOJ刚刚更换了此题的数据,我**,卡精度卡成翔!网上几乎所有的AC代码重交一遍都会WA,估计新数据是为了卡vfk的随机增量算法的!(这里感谢TKD神犇提供的标程)
然后就是各种防卡精度了,基本上就是要注意不能乱用EPS,还有就是加完所有边后,可能还需要从队尾弹掉一部分边,特判下就好了。
另外二分似乎很奇怪,我用另一种二分形式就会wa,尚不清楚为什么会出现这样的问题。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cmath>

#define MAXN 210000
#define EPS 1e-10
#define INF 1e15

using namespace std;

int n;
double PI=acos(-1.0);

int dcmp(double x)
{
    if(fabs(x)<EPS) return 0;
    if(x>EPS) return 1;
    return -1;
}

struct Point
{
    double x,y;
    Point(){}
    Point(double _x,double _y):x(_x),y(_y){}
}p[MAXN];

Point operator-(Point a,Point b)
{
    return Point(a.x-b.x,a.y-b.y);
}

Point operator*(Point a,double b)
{
    return Point(a.x*b,a.y*b);
}

Point operator/(Point a,double b)
{
    return Point(a.x/b,a.y/b);
}

Point operator+(Point a,Point b)
{
    return Point(a.x+b.x,a.y+b.y);
}

double cross(Point a,Point b)
{
    return a.x*b.y-b.x*a.y;
}

struct Line
{
    Point P,v; //有向直线st->ed
    int id;
    double ang; //极角
    Line(){}
    Line(Point _P,Point _v,int _id):P(_P),v(_v),id(_id)
    {
        ang=atan2(v.y,v.x);
        if(ang<0) ang+=2*PI;
    }
}lines[MAXN],deq[MAXN<<1];

int tot,h,t; //tot=线段总数

bool cmp(Line a,Line b)
{
    return a.ang<b.ang;
}

Point getLineIntersec(Line a,Line b) //???
{
    Point u=a.P-b.P;
    double t=cross(b.v,u)/cross(a.v,b.v);
    return a.P+a.v*t;
}

bool onRight(Point a,Line b) //判断点a是否在有向线段b的右手侧
{
    return cross(b.v,a-b.P)<0;
}

inline void Insert(Line l) //将直线l加入半平面交
{
    while(h<t&&onRight(p[t-1],l)) t--;
    while(h<t&&onRight(p[h],l)) h++;
    deq[++t]=l;
    if(h<t&&dcmp(deq[t].ang-deq[t-1].ang)==0) //注意半平面交中不能存在两条极角相同的直线
        t--;
    if(h<t) p[t-1]=getLineIntersec(deq[t],deq[t-1]);
}

bool halfPanelIntersec(int cnt) //判断前cnt关的直线的半平面交是否为空
{
    h=1,t=0;
    for(int i=1;i<=2*n;i++)
        if(lines[i].id<=cnt)
            Insert(lines[i]);
    while(h<t&&onRight(p[t-1],deq[h])) t--;
    return t-h>=2;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        double x,ydown,yup;
        scanf("%lf%lf%lf",&x,&ydown,&yup);
        lines[++tot]=Line(Point(0,ydown/x),Point(1,-x),i);
        lines[++tot]=Line(Point(0,yup/x),Point(-1,x),i);
    }
    sort(lines+1,lines+2*n+1,cmp);
    int lowerBound=1,upperBound=n,ans;
    while(lowerBound<upperBound)
    {
        int mid=(lowerBound+upperBound+1)>>1;
        if(halfPanelIntersec(mid))
        {
            //ans=mid;
            lowerBound=mid;
        }
        else upperBound=mid-1;
    }
    printf("%d\n",lowerBound);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值