题目描述:
有一个无线大的平面,平面上有n条斜率不为零的直线。 有m次询问,每次询问从y轴上的一个点出发往x轴正方向沿一条直线走,最后一个与这n条直线相交的点的 横坐标。 n,m<=50000
题目分析:
根据题意我们可以,如果一条直线为 y=ax+b,另一条直线为 y=cx+d,则我们容易得出他们的交点的横坐标为 。
因此我们可以将这个横坐标看作是两个点(a,b),(c,d)之间的斜率的相反数。
因此我们就可以将题目转化成:在平面内又n个点pi,每次给一个点p,问这个点到平面内所有点的斜率最小值。
而因为最小值相对来说没这么好处理,因此我们则可以将所有点的横坐标取反,则这个题就转化为求斜率的最大值。
对于这个问题,我们就可以通过凸包进行维护。
具体的做法是:我们先根据题目给出的n个直线(将他们看成一个个点对),做一个凸包。因为做出来的凸包上的每一个点是存在一定的单调性的,因此我们可以在凸包上二分查找出跟询问的点p斜率最大的点。(具体的二分操作是如果发现当前选到的凸包上的两点p1,p2与询问的点p的叉积,则证明 pp1的斜率小于pp2,则向左区间二分,以此类推)
当找到斜率最大的点后,不断更新答案即可。
代码:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
const double eps=1e-8;
int sgn(double x){
if(fabs(x)<eps) return 0;
if(x<eps) return -1;
else return 1;
}
struct Point{
double x,y;
int id;
Point(double _x=0, double _y=0,int _id=0){
x=_x,y=_y,id=_id;
}
Point operator -(const Point&q){
return Point(x-q.x,y-q.y);
}
double operator *(const Point &q){
return x*q.x+y*q.y;
}
double operator ^(const Point &q){
return x*q.y-y*q.x;
}
bool operator <(const Point &q){
if(sgn(x-q.x)==0) return y<q.y;
return x<q.x;
}
};
double getk(Point a){
return a.y/a.x;
}
double cross(Point a,Point b,Point c){
return (b-a)^(c-a);
}
Point p[maxn],q[maxn];
double ans[maxn];
void Graham(int n){
int num=0;
for(int i=0;i<n;i++){
if(!p[i].id){
while(num>1&&sgn(cross(q[num-2],q[num-1],p[i]))<=0)
num--;
q[num++]=p[i];
}
else{
int l=0,r=num-1;
while(l<r){
int mid=(l+r)>>1;
if(sgn(cross(p[i],q[mid],q[mid+1]))<=0) r=mid;
else l=mid+1;
}
if(l<num) ans[p[i].id]=max(ans[p[i].id],getk(p[i]-q[l]));
}
}
}
int main()
{
int n,m;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%lf%lf",&p[i].x,&p[i].y),p[i].x=-p[i].x;
}
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%lf%lf",&p[i+n].x,&p[i+n].y);
p[i+n].id=i+1,p[i+n].x=-p[i+n].x;
}
sort(p,p+n+m);
Graham(n+m);
reverse(p,p+n+m);
Graham(n+m);
for(int i=1;i<=m;i++){
if(sgn(ans[i])==0) puts("No cross");
else printf("%.8f
",ans[i]);
}
}