您的位置:首页 > 大数据 > 人工智能

ZOJ 2967——Colorful Rainbows

2015-07-27 14:51 369 查看
题意:有最多5000条线,要求出这些线里面能看到几条。能看到的线是指这条线在一个区间内比其他任何线的y坐标都要大。

思路:根据题目意思,可以看出要求的是在直角坐标系里面最“上面”的那几条线。能看到的线应该组成了一个两边高,中间低的凹的形状。知道了最后要求的大致形状以后,差不多可以想到按从左往右的方式来判断直线是否可见。

先不考虑平行直线,斜率最小的跟斜率最大的直线是一定可见的。从斜率最小的开始考虑。将直线按照斜率从小到大排序。第一条直线即斜率最小的直线,一定是可见的。加入第二条直线,这时因为只有两条直线,所以俩都可见。再加入第三条。第三条的斜率比第二个大,它不可能遮挡住第一条直线,因此它最多只能挡住第二条直线。如果第三条直线与第二条直线的交点的横坐标x1,比第二条直线与第一条直线交点的横坐标x2小或等于,那么第二条直线一定被遮住了(画画图就看出来了)。如果第二条直线没有被遮住,那么这时就可见三条直线。这时,第二条直线有两个交点,将横坐标小的记为p1,将横坐标大的记为p2。

往后每加入一条直线都如此。这样,每一条可见的直线都会记录两个坐标,代表这条直线可见的区间(第一条直线只有与第二条直线一个交点,可以将第一条直线与所有直线中交点最小的给求出来作为它的p1,这样就将第一条直线的处理一般化了)。新加入一条直线以后,如果它会遮挡住直线l,那么在前面判断可见的直线中,斜率比l大的全部都会被遮住。

在判断的过程中,可见的直线相当于一个栈,栈中元素斜率依次增加。每加入一条直线,在里面找不能被挡住的最后一个直线,这条直线以后的所有都看不到了。

我用了两种方法实现,一种是栈顶元素一个一个弹出,另一种是用二分找到最后一个能看到的直线。

代码:

[code]#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<map>
#include<cmath>
#include<assert.h>
#include<iomanip>
#include<assert.h>
using namespace std;
typedef long long ll;

int dcmp(double x){
    if(fabs(x)<1e-9)return 0;
    return x<0?-1:1;
}

struct Point{
    double x,y;
    Point(double _x=0,double _y=0):x(_x),y(_y){}
    bool operator < (const Point &a) const {
        return x<a.x;
    }
    bool operator > (const Point &a) const {
        return x>a.x;
    }
    bool operator <= (const Point &a) const {
        return dcmp(x-a.x)<=0;
    }
    bool operator >= (const Point &a) const {
        return dcmp(x-a.x)>=0;
    }
};

struct Line{
    double a,b;
    Point p1;
    Point p2;
    bool operator < (const Line &l) const{
        if(dcmp(a-l.a)!=0)return a<l.a;
        return b>l.b;
    }
}line[5005];

Point crosspoint(Line l1,Line l2){
    double x = (l2.b-l1.b)/(l1.a-l2.a);
    return Point(x,l1.a*x+l1.b);
}

int main()
{
//    freopen("data.txt","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;++i){
            scanf("%lf%lf",&line[i].a,&line[i].b);
        }
        sort(line,line+n);
        line[0].p1=crosspoint(line[1],line[0]);
        for(int i=1;i<n;++i){
            Point c = crosspoint(line[0],line[i]);
            line[0].p1=min(line[0].p1,c);
        }
        line[0].p1 = Point(line[0].p1.x-1,(line[0].p1.x-1)*line[0].a+line[0].b);//第一条直线的预处理,给它的p1赋值。横坐标减一是为了防止所有点都与第一条直线相交于一点的情况。
        int last=0;
        for(int i=1;i<n;++i){
            if(dcmp(line[i].a-line[i-1].a)==0){
                continue;
            }
            assert(last>=0);
            line[last].p2 = crosspoint(line[last],line[i]);
            if(line[last].p2<=line[last].p1){
                last--;
            }//最后的可见的直线的p2都没有求,新加入直线的时候先把它求出来。
            ///////////////////+++++++++++++++++++++++
//            while(last>=0){
//                Point cp = crosspoint(line[last],line[i]);
//                if(cp<=line[last].p1){
//                    last--;
//                } else {
//                    line[i].p1 = cp;
//                    line[last].p2 = cp;break;
//                }
//            }
//            line[++last]=line[i];
//            continue;
            ////////////////////+++++++++++++++++++++++
            int l=0;
            int r=last;
            while(l<=r){
                int mid = (l+r)>>1;
                Point cp = crosspoint(line[i],line[mid]);
                if(line[mid].p1>line[mid].p2)swap(line[mid].p1,line[mid].p2);
                if(cp>line[mid].p1&&cp<=line[mid].p2){
                    l=r=mid;line[mid].p2=cp;line[i].p1=cp;last = mid;break;
                } else if(cp>line[mid].p2){
                    l=mid+1;
                } else if(cp<=line[mid].p1){
                    r=mid-1;
                } else assert(0);
            }
            line[last+1]=line[i];
            last++;
        }
        printf("%d\n",last+1);
    }
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: