题目概述
二维直角坐标系中,给出n条直线,从y轴正无穷大处向下看,输出可以看见的直线。
解题报告
全部搞在一起看不可能分析清楚,肯定要先两两分析:
设交点是(x,y),那么对于斜率小的直线,大于等于x的部分都被遮住了,而对于斜率大的直线,小于等于x的部分都被遮住了。
假设一条直线小于等于 x1,x2,x3,……,xn 的部分都被遮住了,大于等于 y1,y2,y3……,ym 的部分都被遮住了。设x的最大值为 xmax ,y的最小值为 ymin ,只要 xmax >= ymin ,这条直线就看不到了。
于是问题转化为求
xmax
和
ymin
,
O(n2)
肯定是可以的,但是效率太低。而且由于
y=k1x+b1
,
y=k2x+b2
的交点坐标
x=(b2−b1)/(k1−k2)
,同时被k和b影响,常用的求极值方法派不上用场,所以我们要想其他方法。
……
反正我是没想到!所以上述想法很难实现!看了讨论发现了个关键词:半平面交。虽然我不太了解半平面交,但是我发现不一定要用代数做法,可以用几何做法解决这道题。
先按照k排序(k相同的只留下b最大的),然后储存一个栈,表示目前能看见的直线。每次加入一条直线时,求出这条直线与栈顶的交点A,然后求出栈顶和栈顶下面元素的交点B,如果A的横坐标小于等于B的横坐标,那么当前栈顶就被遮掉了(也就是简化版半平面交):
最后栈内存在的元素就可见直线。
示例程序
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=50000;
int n,top,stk[maxn+5];
struct Point
{
double x,y;
Point (double X=0,double Y=0) {x=X;y=Y;}
};
int fcmp(double x,double y) {if (fabs(x-y)<=1e-10) return 0;if (x<y) return -1; else return 1;}
struct Line
{
double k,b;int id;
bool operator < (const Line &a) const {return fcmp(k,a.k)<0||!fcmp(k,a.k)&&fcmp(b,a.b)>0;}
};
Line l[maxn+5];
Point getPoint(const Line &a,const Line &b) {double x=(b.b-a.b)/(a.k-b.k);return Point(x,a.k*x+a.b);}
int main()
{
freopen("program.in","r",stdin);
freopen("program.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%lf%lf",&l[i].k,&l[i].b),l[i].id=i;
sort(l+1,l+1+n);
int num=1;for (int i=2;i<=n;i++) if (l[i-1].k<l[i].k) l[++num]=l[i];n=num;
for (int i=1;i<=n;i++)
{
while (top>1&&fcmp(getPoint(l[stk[top]],l[i]).x,getPoint(l[stk[top-1]],l[stk[top]]).x)<=0) top--;
//删除堆顶
stk[++top]=i;
}
for (int i=1;i<=top;i++) stk[i]=l[stk[i]].id;
sort(stk+1,stk+1+top);
for (int i=1;i<=top;i++) printf("%d ",stk[i]);
return 0;
}