2018.07.03 BZOJ 1007: [HNOI2008]水平可见直线(简单计算几何)

时间:2021-12-31 08:24:47

1007: [HNOI2008]水平可见直线

Time Limit: 1 Sec Memory Limit: 162 MB

Description

  在xoy直角坐标平面上有n条直线L1,L2,…Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为可见的,否则Li为被覆盖的.

例如,对于直线:L1:y=x; L2:y=-x; L3:y=0则L1和L2是可见的,L3是被覆盖的.

给出n条直线,表示成y=Ax+B的形式(|A|,|B|<=500000),且n条直线两两不重合.求出所有可见的直线.

Input

  第一行为N(0 < N < 50000),接下来的N行输入Ai,Bi

Output

  从小到大输出可见直线的编号,两两中间用空格隔开,最后一个数字后面也必须有个空格

Sample Input

3

-1 0

1 0

0 0

Sample Output

1 2

这又是一道基础的计算几何,我简单说说我的思路吧。

如果我们把所有的线段按照他们的k" role="presentation" style="position: relative;">kk排序,相当于就是按照极角排序,冷静分析一下,显然排序过后的第一条直线和最后一条直线一定可以看到,然后再发散一下思维,如果我们将直线的方向固定,让斜率为负数的直线的箭头朝下,斜率为正数的直线箭头朝上,然后搞半平面交,在搞半平面交的同时如果有直线被弹出,那这根直线显然不能计入答案(请各位务必想清楚原因),最后for" role="presentation" style="position: relative;">forfor循环判一下标记就没了。

小技巧:这个时候我们只关心直线的位置关系,所以没有必要做纯种的半平面交,只需转化为点与点之间的关系就可以了,具体细节见代码吧。

代码如下:

#include<bits/stdc++.h>
#define N 50005
#define eps 1e-8
using namespace std;
struct line{
    double x,y;
    int id;
    inline bool operator<(const line&a)const{return x==a.x?y>a.y:x<a.x;}
}l[N];
int n,head=0,q[N];
bool vis[N];
inline double calc(int i,int j){return (l[i].y-l[j].y)/(l[j].x-l[i].x);}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%lf%lf",&l[i].x,&l[i].y),l[i].id=i;
    sort(l+1,l+n+1);
    q[++head]=1;
    for(int i=1;i<=n;++i){
        if(l[i].x-l[i-1].x<eps)continue;
        while(head>1&&calc(i,q[head])<=calc(q[head],q[head-1]))--head;
        q[++head]=i;
    }
    for(int i=1;i<=head;++i)vis[l[q[i]].id]=1;
    for(int i=1;i<=n;++i)if(vis[i])printf("%d ",i);
    return 0;
}