线段树扫描线总结

通过扫描线求一个由多个图形构成的图形的面积,就是用一条拟定出的线不断的划分每一个小矩形的面积,这一点并不难理解,之前困扰我的是如何用代码实现:因为同一个高度上的小矩形可能是不连续的多个,而且小矩形的低可能是double类型的数据。

在网上搜到一道基础的扫描线题目,读完题目后直接看了题解,看一下如何用代码实现扫描线的功能,所看代码已列在下面,关于上面的两个问题我也有了答案。

一、同一个高度的扫描线是可以有多条的,但是每个扫描线划分的小矩形都是由(底*高)的数学公式得来的,如果是同一高度的扫描线,意味着这两条线之间组成的矩形的高为0,所以小矩形的面积也是0,关键是通过定义先后顺序使得(后一条扫描线的高 – 前一条扫描线的高),这样可以保证在同一高度上的全部扫描线都完成更新操作后才会有这一高度小矩形的底边完全更新。

二、在处理doubl类型的x轴坐标时,可以通过离散化的方法解决,并且对于重复的x坐标进行删除操作,这样是为了在后面的处理中更加的方便,其实很多涉及double类型或者数据的范围过大的情况都需要使用离散化进行处理。

另外题解中还有一点比较巧妙的就是重新定义了一棵树用来存储涉及到的x坐标范围,算得上是一次优化吧,在最后求底边长的时候直接用sum[1]即可。

此外在这一道题目的题解代码中还有一点很重要的东西,就是用-1来表示两个字数的状态(即是否包含在矩形中)不一致,因为用1来表示下边的值,用-1表示上边的值,又因为任何一个矩形必定是先有了下边才会有上边,所以每一条扫描线在正常情况下的值都不会为-1,开始看代码的时候以为表示的是两个子区间的值都为-1,后来才想明白,也算是代码的巧妙之处吧。

 

 

 

下边是我结合自己的认识重新写的扫面线的代码。

 

 

 

 

 

#include<cstdio>

#include<cstring>

#include<algorithm>

using namespace std;

const int MAX=1e5;

struct Line{

    double x1,x2;

    double h;

    int c;

    Line(){}

    Line(double x1,doublex2,double h,int c):x1(x1),x2(x2),h(h),c(c){}

    bool operator <(constLine &temp)const{

        return h<temp.h;

    }

 

}tree[MAX];

double sum[MAX<<2];

double X[MAX];

int C[MAX<<2];

void PushDown(int node,int left,int right){

    intmid=(left+right)>>1;

    if(C[node]!=-1){

       C[node<<1]=C[node<<1|1]=C[node];

       sum[node<<1]=(C[node]?(X[mid+1]-X[left]):0);

       sum[node<<1|1]=(C[node]?(X[right+1]-X[mid+1]):0);

    }

}

void PushUp(int i,int left,int right){

    if(C[i<<1]==-1||C[i<<1|1]==-1)C[i]=-1;

    elseif(C[i<<1]!=C[i<<1|1]) C[i]==-1;

    else C[i]=C[i<<1];

   sum[i]=sum[i<<1]+sum[i<<1|1];

}

void Build(int node,int left,int right){

    if(left==right) {

        C[node]=0;//存疑:为什么不是left作为数组下标

        sum[node]=0.0;//存疑:为什么不是left作为数组下标

        return ;

    }

    intmid=(left+right)>>1;

   Build(node<<1,left,mid);

   Build(node<<1|1,mid+1,right);

    PushUp(node,left,right);

}

void Update(int ql,int qr,int value,int node,int left,int right){

    if(ql<=left&&qr>=right){

        //C[node]==-1说明下面的两个节点存在矩形上边或者下面的两个节点的value值不同

        //正常情况下,C[i]的值是不会等于-1的,最多只能为0,-1说明了当前的状态不清楚

        if(C[node]!=-1){

            C[node]+=value;

           sum[node]=C[node]?(X[right+1]-X[left]):0;

            return ;

        }

    }

    PushDown(node,left,right);

    intmid=(left+right)>>1;

    if(ql<=mid)Update(ql,qr,value,node<<1,left,mid);

    if(qr>mid)Update(ql,qr,value,node<<1|1,mid+1,right);

    PushUp(node,left,right);

}

int bin(double key,int n,double arr[]){

    int l=1,r=n;

    while(r>=l){

        intmid=(l+r)>>1;

        if(arr[mid]==key){

            return mid;

        }

        elseif(arr[mid]>key){

            r=mid-1;

        }

        else l=mid+1;

    }

    return -1;

}

int main(){

    int Case;//输入数据的组数

    int case_num=0;//

   while(scanf("%d",&Case)==1&&Case){

        //X坐标个数,扫描线的个数

        intX_account=0,Line_account=0;

        for(inti=1;i<=Case;i++){

            doublex1,x2,y1,y2;

           scanf("%1f%1f%1f%1f",&x1,&y1,&x2,&y2);

            X[++X_account]=x1;

            X[++X_account]=x2;

           tree[++Line_account]=Line(x1,x2,y1,1);

           tree[++Line_account]=Line(x1,x2,y2,-1);

        }

       sort(X+1,X+X_account+1);

       sort(tree+1,tree+Line_account+1);

        int account=1;//不重叠X坐标的个数,account-1表示区间个数

        for(inti=2;i<=X_account;i++){

            if(X[i]!=X[i-1])X[++account]=X[i];

        }

        Build(1,1,account-1);

        double finalArea=0.0;

        for(inti=1;i<Line_account;i++){

            intl=bin(tree[i].x1,account,X);

            intr=bin(tree[i].x2,account,X)-1;

            if(l<=r)Update(l,r,tree[i].c,1,1,account);

            //sum[1]是线段树的最上层节点,表示这条线共有多长

            //在同一高度的扫描线的高度差为0,和底边的乘积为0,只有最后一次才会被记入总面积

            finalArea+=sum[1]*(tree[i+1].h-tree[i].h);

        }

        printf("Test case#%d\nTotal explored area: %.2lf\n\n",++Case,finalArea );

 

    }

    return 0;

}

但是运行时候出现了一点问题,稍后做修改

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值