计算几何(二分答案or二分搜索)
Description
uncle-lu对计算几何有着浓厚的兴趣。他经常对着平面直角坐标系发呆,思考一些有趣的问 题。今天,他想到了一个十分有意思的题目:
首先,uncle-lu会在x轴正半轴和y轴正半轴分别挑选n个点。随后,他将x轴的点与y轴 的点一一连接,形成n条线段,并保证任意两条线段不相交。uncle-lu确定这种连接方式有且仅有一种。最后,uncle-lu会给出m个询问。对于每个询问,将会给定一个点P(xp,yp),问线段OP(O为坐标原点)与n条线段会产生多少个交点?
Input
第1行包含一个正整数n,表示线段的数量;
第2行包含n个正整数,表示uncle-lu在x轴选取的点的横坐标;
第3行包含n个正整数,表示uncle-lu在y轴选取的点的纵坐标;
第4行包含一个正整数m,表示询问数量;
随后m行,每行包含两个正整数xp和yp,表示询问中给定的点的横、纵坐标。
Output
共m行,每行包含一个非负整数,表示你对这条询问给出的答案。
Sample Input 1
3
4 5 3
3 5 4
2
1 1
3 3
Sample Output 1
0
3
Hint
3条线段分别为:(3, 0)−(0, 3)、(4, 0)−(0, 4)、(5, 0)−(0, 5)
(0, 0)−(1, 1)不与他们有交点,答案为0
(0, 0)−(3, 3)与三条线段均有交点,答案为3。
数据范围:
对于100%的数据:n,m≤10 ^ 5 ,1≤x,y≤2^31。
Time Limit
1000MS
Memory Limit
256MB
分析:通过画图,不难得知,只有将x、y正半轴上2n个点这样匹配,才可以连出n条不相交的线段。
即数值小的点和数值小的点匹配,才能保证线段不相交。
由高中平面几何知识:对于每条线段(0,yi)-(xi,0),可列 截距式方程 x/xi + y/yi =1,当某个点(xp,yp)带入方程后有xp/xi + yp/yi >=1,说明这个点不在这条线段下方,则线段(0,0)-(xp,yp)与包括这条线段在内的左侧所有线段都各有1个交点,反之则点(xp,yp)在这条线段下方,线段(0,0)-(xp,yp)与这条线段没有交点。
将式x/xi + y/yi =1变换,我们就可以通过计算 xp * yi + yp * xi - xi * yi 的值判断线段(0,0)-(xp,yp)与线段(0,yi)-(xi,0)有没有交点。到这里我们已经能看出这题含有单调性,可以二分。我们 对线段从左往右进行编号 ,对编号进行二分枚举,如果编号i对应的 xp * yi + yp * xi - xi * yi 大于等于0,说明有交点,可以再探索更大的编号;否则编号要换得小一点。
由此可见,我们需要对输入数据进行预处理,将x和y从小到大排序,这样就自然得到线段从左往右的序列。另外,在做这题时,看题目数据范围,所有输入数据都可以不开long long,但是计算 xp * yi + yp * xi - xi * yi 时必须转换为long long防止溢出。
参考代码:
#include<stdio.h>
#include<algorithm>
int n,m;//线段数,询问数
int x[100001],y[100001];//线段的x坐标,y坐标
int X,Y;//查询坐标
//计算叉积,判断相交
long long int count(int t)
{//由数据范围,必须要转为long long,防止进行乘法或加分时溢出
return (long long)X*y[t]+(long long)Y*x[t]-(long long)x[t]*y[t];
}
//计算交点数
int judge()
{//如果一个交点也没有,ans不会再被赋值,出循环时仍为0
//左开右开区间写法,解区间为[1,n]
int left=0,right=n+1,ans=0,mid;
long long int temp;
while(left+1!=right)
{//既然要找最大的线段编号,不妨向上取整
mid=left+((right-left+1)>>1);
temp=count(mid);
if(temp>=0){//点不在编号mid线段下方
ans=mid;//从线段1到线段mid都与所问线段线段相交
left=mid;//看看更大的编号会不会相交
}//点在编号mid线段下方,说明编号更小才有可能相交
else right=mid;
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",x+i);
}
for(int i=1;i<=n;i++)
{
scanf("%d",y+i);
}
scanf("%d",&m);
std::sort(x+1,x+n+1);//数据预处理
std::sort(y+1,y+n+1);
while(m)
{
scanf("%d%d",&X,&Y);
printf("%d\n",judge());
--m;
}
return 0;
}