zoj2967 Colorful Rainbows(凸包 排序 栈)

来源:互联网 发布:吉力贝怪味糖 知乎 编辑:程序博客网 时间:2024/05/19 01:06

问题分析:
  这道题是去年(2008)浙江省赛的题目,因此估计比较多人留意,我写本文的时候一共有90多个AC了。问题很简单,就是给出 n条斜率存在的直线,称 y坐标比较大的为比较“高”的,而一些直线比其他直线高的部分会把那些较低的部分遮盖,问从上往下看能看到多少条不同的直线(只需要看到一部分就好了)。
  数据范围 n 达到了 5000,时限 1s。因此(N^2)的想法基本上可以直接淘汰。
  一说本题是半平面交,但虽然问题差不多,其实跟一般的半平面交解法是有一定差异的,因为本体的所有“半平面”都是向上的。


算法介绍:
  从凸包的 Graham扫描法中得到启示,我使用的是一种类似的排序/堆栈的办法来解决这个问题,下面是这个算法的描述。

clip_image001
  如上图,有 n = 5 条直线,但是可以看见的是 4 条。因为斜率存在,因此可以用斜截式方程 y = k x + b来描述这些直线,保存浮点数对 <k, b>表示一条直线。  第一步是对这些直线进行按斜率排序,下面的图示可以表达这一过程:

clip_image002clip_image003clip_image004clip_image005clip_image006

  下一步呢,我们需要剔除掉那些平行的直线,因为平行的直线处于上方的必然会完全盖住处于下方的,因此我们把第三条直线删掉:

clip_image002[1]clip_image003[1]clip_image007clip_image008


  然后枚举这些排好序的直线,并且用一个堆栈,存放一条射线(一条直线,以及它左端的一个 x 坐标)
  初始化的时候,将第一条直线放到堆栈当中,然后开始点的 x 值取一个很小的数就可以了。
  然后,每次加入一条直线,都与栈顶的直线进行相交,得到交点坐标 (x0, y0),如果得到的 x0 比栈顶的射线的 x坐标小,证明这条新加入的直线将栈顶的那条直线可见的部分(也就是射线)都遮盖住了,那么就从栈中把这条射线弹出,直到栈中剩下一条直线或者新交点的x0 比栈顶射线的 x 坐标值大为止。
  然后将这个焦点的 x0 作为新的射线开头坐标,和枚举到的直线一起,压入栈中。
  这样操作完之后,栈中的射线数目,就等于可以看见的直线数目。其实道理很简单,因为给出两条直线 L1 和 L2,如果 L1 的斜率< L2 的斜率,那么 L1 被 L2 遮盖的一定是后半部分,而 L2 被 L1遮盖的必然是前半部分。
  因此,任举一条直线,它被所有斜率比他小的直线遮盖剩下的,一定是一条向右的射线。而它依然可见的充要条件,就是比它斜率大的直线并没有把它的剩下部分给遮盖完。这样再回想刚才的操作,就不难想明白这个算法的道理了。
  下面是效率分析,在排序的时候需要一个 O(nlogn) 的操作,然后剩下的因为每条直线入栈都是一次,顶多出栈一次,因此是 O(n)的复杂度,总体复杂度为 O(nlogn),满足要求。

#include <iostream>#include <cstdio>#include <algorithm>#include <cmath>#include <stack>#define MIN 0.00001#define MAX 1<<30using namespace std;struct node{    double k,b;} line[10000];int num;double x[10000];bool cmp(node a,node b){    return a.k<b.k;}int judge(double a,double b){    int i;    for(i=0; i<num; i++)    {        if(fabs(a-line[i].k)<MIN&&(b<=line[i].b))            return 0;        if(fabs(a-line[i].k)<MIN&&(b>line[i].b))        {            line[i].b=b;            return 0;        }    }    return 1;}double chaji(node l1,node l2){    return (l1.b-l2.b)/(l2.k-l1.k);}void slove(){    int i;    stack<int> s;    s.push(0);    for(i=0; i<=num; i++)        x[i]=-MAX;    for(i=1; i<num; i++)    {        int pos=s.top();        double rex=chaji(line[pos],line[i]);        while(rex<x[pos]||fabs(rex-x[pos])<=MIN)        {            s.pop();            pos=s.top();            rex=chaji(line[pos],line[i]);        }        x[i]=rex;        s.push(i);    }    printf("%d\n",s.size());}int main(){    int i,T,n;    double a,b;    scanf("%d",&T);    while(T--)    {        num=0;        scanf("%d",&n);        for(i=0; i<n; i++)        {            scanf("%lf%lf",&a,&b);            if(judge(a,b))            {                line[num].k=a;                line[num].b=b;                num++;            }        }        sort(line,line+num,cmp);        slove();    }    return 0;}

0 0