【HNOI 2012】射箭

传送门


Problem

沫沫最近在玩一个二维的射箭游戏,这个游戏中的 x x x 轴在地面,第一象限中有一些竖直线段作为靶子,任意两个靶子都没有公共部分,也不会接触坐标轴。

沫沫控制一个位于 ( 0 , 0 ) (0,0) (0,0) 的弓箭手,可以朝 0 0 0 90 90 90 中的任意角度(不包括 0 0 0 度和 90 90 90 度),以任意大小的力量射出带有穿透能力的光之箭。由于游戏中没有空气阻力,并且光之箭没有箭身,箭的轨迹会是一条标准的抛物线,被轨迹穿过的所有靶子都认为被沫沫射中了,包括那些只有端点被射中的靶子。

这个游戏有多种模式,其中沫沫最喜欢的是闯关模式。

在闯关模式中,第一关只有一个靶子,射中这个靶子即可进入第二关,这时在第一关的基础上会出现另外一个靶子,若能够一箭双雕射中这两个靶子便可进入第三关,这时会出现第三个靶子。依此类推,每过一关都会新出现一个靶子,在第 k k k 关必须一箭射中前 k k k 关出现的所有 k k k 个靶子才能进入第 k + 1 k+1 k+1 关,否则游戏结束。

沫沫设法获得了每一关出现的靶子的位置,想让你告诉她,最多能通过多少关。


Solution

设抛物线的解析式为 y = a x 2 + b x y=ax^2+bx y=ax2+bx,则每一关的约束条件为 y 1 ≤ a x 2 + b x ≤ y 2 y_1\le ax^2+bx\le y_2 y1ax2+bxy2,也即 y 1 x ≤ a x + b ≤ y 2 x \frac{y_1}x\le ax+b\le \frac {y_2}x xy1ax+bxy2

这个时候要注意, x , y 1 , y 2 x,y_1,y_2 x,y1,y2 是已知量, a , b a,b a,b 是变量,不要弄混了。

也就是说,我们现在就有了两个约束条件: x a + b ≥ y 1 x , x a + b ≤ y 2 x xa+b≥\frac{y_1}x,xa+b\le \frac {y_2}x xa+bxy1,xa+bxy2

然后就想到半平面交,判断最后的交集是否为空(要注意直线的方向)。

现在的问题是怎么计算最多能通过多少关

由于这道题中有单调性,我们可以用二分答案。我们可以先把直线用极角排好序,每次就只考虑前 m i d mid mid 关的直线,然后用半平面交即可。

注意:

  1. 这道题的精度要求比较大,要用 l o n g    d o u b l e \mathrm{long\;double} longdouble
  2. 由题意易知 a &lt; 0 , b &gt; 0 a&lt;0,b&gt;0 a<0,b>0,所以一开始要加入四个半平面来约束这一条件。

Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
#define eps 1e-12
#define inf 1e12
using namespace std;
int n,num;
struct point{
	long double x,y;
	point(){}
	point(long double x,long double y):x(x),y(y){}
	friend point operator+(const point &a,const point &b)  {return point(a.x+b.x,a.y+b.y);}
	friend point operator-(const point &a,const point &b)  {return point(a.x-b.x,a.y-b.y);}
	friend point operator*(const point &a,long double b)  {return point(a.x*b,a.y*b);}
	friend long double dot(const point &a,const point &b)  {return a.x*b.x+a.y*b.y;}
	friend long double cross(const point &a,const point &b)  {return a.x*b.y-a.y*b.x;}
};
struct line{
	point s,t;int id;long double angle;
	line(){}
	line(point s,point t,int id):s(s),t(t),id(id){}
}l[N],a[N],Q[N];
bool operator<(const line &p,const line &q)  {return (p.angle!=q.angle)?p.angle<q.angle:cross(q.t-p.s,q.s-p.s)>0;}
point inter(line a,line b){
	long double S1=cross(b.s-a.s,b.t-a.s);
	long double S2=cross(b.s-a.t,b.t-a.t);
	long double k=S1/(S1-S2);
	return a.s+(a.t-a.s)*k;
}
bool Judge(line a,line b,line c){
	point p=inter(a,b);
	return cross(p-c.s,c.t-c.s)>0;
}
bool check(int mid){
	int tot=0;
	for(int i=1;i<=num;++i)
		if(l[i].id<=mid&&l[i].angle!=a[tot].angle)
			a[++tot]=l[i];
	int L=1,R=0;
	Q[++R]=a[1],Q[++R]=a[2];
	for(int i=3;i<=tot;++i){
		while(L<R&&Judge(Q[R-1],Q[R],a[i]))  --R;
		while(L<R&&Judge(Q[L+1],Q[L],a[i]))  ++L;
		Q[++R]=a[i];
	}
	while(L<R&&Judge(Q[R-1],Q[R],Q[L]))  --R;
	while(L<R&&Judge(Q[L+1],Q[L],Q[R]))  ++L;
	return R-L>1;
}
int main(){
	scanf("%d",&n);
	long double x,y1,y2;
	for(int i=1;i<=n;++i){
		scanf("%Lf%Lf%Lf",&x,&y1,&y2);
		l[++num]=line(point(0,y1/x),point(1,y1/x-x),i);
		l[++num]=line(point(1,y2/x-x),point(0,y2/x),i);
	}
	l[++num]=line(point(-inf,eps),point(-eps,eps),0);
	l[++num]=line(point(-eps,eps),point(-eps,inf),0);
	l[++num]=line(point(-eps,inf),point(-inf,inf),0);
	l[++num]=line(point(-inf,inf),point(-inf,eps),0);
	for(int i=1;i<=num;++i)  l[i].angle=atan2(l[i].t.y-l[i].s.y,l[i].t.x-l[i].s.x);
	sort(l+1,l+num+1);
	int l=1,r=n;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(mid))  l=mid;
		else  r=mid-1;
	}
	printf("%d",l);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值