【bzoj1007】[HNOI2008]水平可见直线 半平面交/单调栈

题目描述

在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条直线两两不重合.求出所有可见的直线.

输入

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

输出

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

样例输入

3
-1 0
1 0
0 0

样例输出

1 2



题解

半平面交/单调栈

其实一开始学半平面交就是为了这道题,于是码了一发。其中需要在最上方添加一条辅助直线(辅助半平面)以使得半平面交为封闭图形。

代码:

#include <cmath>
#include <cstdio>
#include <algorithm>
#define N 50010
using namespace std;
struct point
{
    double x , y;
    point() {}
    point(double a , double b) {x = a , y = b;}
    point operator+(const point &a)const {return point(x + a.x , y + a.y);}
    point operator-(const point &a)const {return point(x - a.x , y - a.y);}
    point operator*(const double &a)const {return point(a * x , a * y);}
}p[N];
struct line
{
    point p , v;
    double ang;
    int id;
}a[N] , q[N];
inline double cross(point a , point b) {return a.x * b.y - a.y * b.x;}
inline bool left(line a , point b) {return cross(a.v , b - a.p) >= 0;}
inline point inter(line a , line b)
{
    point u = a.p - b.p;
    double tmp = cross(b.v , u) / cross(a.v , b.v);
    return a.p + a.v * tmp;
}
bool cmp1(const line &a , const line &b)
{
    return a.ang == b.ang ? left(a , b.p) : a.ang < b.ang;
}
bool cmp2(const line &a , const line &b)
{
    return a.id < b.id;
}
int main()
{
    int n , i , tot = 1 , l = 1 , r = 1;
    double k , b;
    scanf("%d" , &n);
    for(i = 1 ; i <= n ; i ++ ) scanf("%lf%lf" , &k , &b) , a[i].p = point(0 , b) , a[i].v = point(-1 , -k);
    a[n + 1].p = point(0 , 1e18) , a[n + 1].v = point(1 , 0);
    a[n + 2].p = point(-3e9 , 0) , a[n + 2].v = point(0 , 1);
    a[n + 3].p = point(3e9 , 0) , a[n + 3].v = point(0 , -1);
    for(i = 1 ; i <= n + 3 ; i ++ ) a[i].ang = atan2(a[i].v.y , a[i].v.x) , a[i].id = i;
    sort(a + 1 , a + n + 4 , cmp1);
    for(i = 2 ; i <= n + 3 ; i ++ )
        if(a[i].ang != a[i - 1].ang)
            a[++tot] = a[i];
    q[1] = a[1];
    for(i = 2 ; i <= tot ; i ++ )
    {
        while(l < r && left(a[i] , p[r - 1])) r -- ;
        while(l < r && left(a[i] , p[l])) l ++ ;
        q[++r] = a[i];
        if(l < r) p[r - 1] = inter(q[r - 1] , q[r]);
    }
    while(l < r && left(q[l] , p[r - 1])) r -- ;
    sort(q + l , q + r + 1 , cmp2);
    for(i = l ; i <= r - 3 ; i ++ ) printf("%d " , q[i].id);
    return 0;
}

然后就被D了一顿= =

事实上,本题并不需要复杂的码农半平面交= =

考虑将所有点按照斜率从小到大排序,去重,那么就可以直接把所有直线压入栈中,不满足的弹出。

具体地,如果栈顶直线被当前直线和次栈顶直线完全覆盖,那么就把栈顶弹出。这样维护一个单调栈即可。

如何判断站定直线被覆盖?两种方法:判断栈顶与次栈顶直线的交点是否在当前直线下方(半平面交的写法),或判断栈顶与次栈顶直线的交点是否在当前直线与栈顶直线的交点的右侧(单调栈的写法)。

两种写法均可行,代码中写了第二种,稍微会短一些。

#include <cstdio>
#include <algorithm>
#define N 50010
using namespace std;
struct data
{
	double k , b;
	int id;
	bool operator<(const data &a)const {return k == a.k ? b > a.b : k < a.k;}
}a[N] , s[N];
int ans[N];
inline double inter(data a , data c)
{
	return (a.b - c.b) / (c.k - a.k);
}
int main()
{
	int n , i , tot = 1 , top = 1;
	scanf("%d" , &n);
	for(i = 1 ; i <= n ; i ++ ) scanf("%lf%lf" , &a[i].k , &a[i].b) , a[i].id = i;
	sort(a + 1 , a + n + 1);
	for(i = 2 ; i <= n ; i ++ )
		if(a[i].k != a[i - 1].k)
			a[++tot] = a[i];
	s[1] = a[1];
	for(i = 2 ; i <= tot ; i ++ )
	{
		while(top > 1 && inter(s[top - 1] , s[top]) >= inter(s[top] , a[i])) top -- ;
		s[++top] = a[i];
	}
	for(i = 1 ; i <= top ; i ++ ) ans[s[i].id] = 1;
	for(i = 1 ; i <= n ; i ++ )
		if(ans[i])
			printf("%d " , i);
	return 0;
}
时间: 2024-10-14 23:44:10

【bzoj1007】[HNOI2008]水平可见直线 半平面交/单调栈的相关文章

[日常摸鱼]bzoj1007[HNOI2008]水平可见直线-半平面交(对偶转凸包)

不会写半平面交-然后发现可以转成对偶凸包问题 具体见这里:http://trinkle.blog.uoj.ac/blog/235 相关的原理我好像还是不太懂-orz #include<cstdio> #include<algorithm> const int N=50005; inline int read() { int s=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}

bzoj 1007 水平可见直线 半平面交稀里糊涂的过了...

题意:按y=Ax+B的形式给出n(<=50000)条直线,求从y值为无穷大的地方向下看能看到的直线编号 一看到题目就想到半平面交,以每条直线的上方为一个半平面,求半平面的交,交集中存在的直线就是能看到的直线 但是写出来之后发现样例都过不了... 对于样例,如果允许半平面在边界处重叠那么答案是1,2,3,如果不允许只有1.. 然后抱着试一试的心理交上去了,结果竟然直接AC了.. 后来看题解只需要考虑交点x坐标.. bzoj 1007 水平可见直线 半平面交稀里糊涂的过了...,布布扣,bubuko

bzoj1007: [HNOI2008]水平可见直线(单调栈)

1007: [HNOI2008]水平可见直线 题目:传送门 题解: 蒟蒻在bzoj上做的第一道计算几何 其实这道题并不难...(所以我A了) 仔细想想不难发现,其实我们只需要维护一个下凸的图形... 只有在这个图形上的直线才不会被覆盖,也就是可以被上帝直线看到的孩子 为什么呢...自己画个图模拟吧. 那么具体的做法我们就要采用单调栈啦! 把给出的直线按照斜率从小到大排序(如果斜率相同的话就按照b来排) 然后一个一个放入我们强大的单调栈中,在加入的同时我们当然还要进行维护: 如果栈顶的直线与新加直

[bzoj1007][HNOI2008][水平可见直线]

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

[BZOJ1007] [HNOI2008] 水平可见直线 (凸包)

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 从

[BZOJ1007][HNOI2008]水平可见直线 计算几何

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1007 可以发现题目求的就是一个下凸包,把直线按斜率排序,再来维护凸包就好了.可以发现下凸包上的拐点横坐标单增.同时注意处理斜率相同的直线的情况. 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const double eps=1e-14;

【BZOJ1007】[HNOI2008]水平可见直线 半平面交

[BZOJ1007][HNOI2008]水平可见直线 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

bzoj 1007 [HNOI2008]水平可见直线(单调栈)

1007: [HNOI2008]水平可见直线 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 5120  Solved: 1899[Submit][Status][Discuss] Description 在xoy直角坐标平面上有n条直线L1,L2,...Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为可见的,否则Li为被覆盖的.     例如,对于直线:     L1:y=x; L2:y=-x; L3:y=0     则L1和L

【BZOJ1007】水平可见直线(单调栈)

[BZOJ1007]水平可见直线(单调栈) 题解 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 < 50