欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

计算几何(二分答案or二分搜索)

程序员文章站 2022-06-02 17:30:54
...

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条不相交的线段。
计算几何(二分答案or二分搜索)
数值小的点和数值小的点匹配,才能保证线段不相交。
由高中平面几何知识:对于每条线段(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;
}
相关标签: 算法基础题