线段树+扫描线(有关扫描线的理解)

线段树+扫描线(有关扫描线的理解)扫描线 下面是来自 soar 转载的一篇博客 这篇博客解决了我对算区间长度时的不理解 实际上这个线段树的叶子节点保存的是这个点 x 坐标到下一个 x 坐标 排序后的 的区间长度 题意 二维平面有 n 个平行于坐标轴的矩形 现在要求出这些矩形的总面积 重叠部分只能算一次 分析 线段树的典型扫描线用法 nbsp nbsp nbsp nbsp nbsp nbsp 首先假设有下图两个矩阵 我们如果用扫描线的方法如何计算它们的总面积呢

扫描线:

下面是来自soar转载的一篇博客。

这篇博客解决了我对算区间长度时的不理解。实际上这个线段树的叶子节点保存的是这个点x坐标到下一个x坐标(排序后的)的区间长度。

题意:

二维平面有n个平行于坐标轴的矩形,现在要求出这些矩形的总面积. 重叠部分只能算一次.

分析:

线段树的典型扫描线用法.

       首先假设有下图两个矩阵,我们如果用扫描线的方法如何计算它们的总面积呢?

线段树+扫描线(有关扫描线的理解)

首先我们将矩形的上下边分为上位边(即y坐标大的那条平行于x轴的边),和下位边(y坐标小的平行于x轴的边).然后我们把所有矩形的上下位边按照他们y坐标从小到大排序,可以得到4条扫描线:

线段树+扫描线(有关扫描线的理解)

又因为上面2个矩形有4个不同的浮点数x坐标,所以我们需要把x坐标离散化,这样才能用线段树来维护信息.所以我们这样离散化:

线段树+扫描线(有关扫描线的理解)

 

由上图可知,4个不同的x坐标把x轴分成了3段有效的区间.这里要注意我们线段树中每个叶节点(控制区间[L,L])不是指X[L]坐标,而是指区间[X[L],X[L+1]].线段树中其他节点控制的区间[L,R],也是指的x坐标轴的第L个区间到第R个区间的范围,也就是X[L]到X[R+1]坐标的范围.

然后我们Y坐标从小到大的顺序读入每条扫描线,并维护当前我们所读入的所有扫描线能有效覆盖X轴的最大长度sum[1].这里特别要注意如果我们读入的扫描线是矩形的下位边,那么我们就使得该范围的标记cnt位+1,如果是上位边,那么该范围的cnt就-1.所以如果cnt=0时,表示该节点控制的范围没有被覆盖,只要cnt!=0 就表示该节点控制的几块区间仍然被覆盖.

下面依次读入每条矩阵边,来一一分析,首先是读入第一条矩阵边:

线段树+扫描线(有关扫描线的理解)

 

我们读入了矩形1的下位边,那么该区域的cnt就+1=1了,所以该区域[10,20]就被覆盖了,然后可以推出整个区域被覆盖的长度是10.再根据第二条扫描线离第一条扫描线的高度差为5.所以不管你第二条扫描线是哪个矩形的什么边,或者能覆盖到X轴的什么范围,我上图中蓝色的矩形面积肯定是要算到总面积里面去的.即总面积ret+=sum[1]*(扫描线2的高度-扫描线1的高度). (想想看是不是这样).

下面读第二条扫描线:

线段树+扫描线(有关扫描线的理解)

由于第二条扫描线也是下位边,所以[15,20]和[20,25]的cnt+1.使得我们覆盖的范围变成了[10,25]了,并且第3条扫描线在20高度,所以这次我们必然增加的面积是上面深蓝色的长条=sum[1]*(扫描线3的高度-扫描线2的高度).

下面我们要读第三条扫描线了:

线段树+扫描线(有关扫描线的理解)

由于第三条扫描线是区间[10,20]的上位边,所以对应区间的cnt要-1,所以使得区间[10,15]的cnt=0了,而[15,20]区间的cnt-1之后变成了1.[20,25]的cnt仍然为1,不变.所以当前覆盖的有效x轴长度为10,即区间[15,25].所以增加的面积是图中褐色的部分.

到此,矩形的面积和就算出来了.由于对于任一矩形都是先读下位边(cnt+1),再读上位边(cnt-1)的,所以在更新线段树的过程中,任意节点的cnt都是>=0的.

下面说代码实现部分:

首先建立一个node结构体用来保存每条扫描线,node中有信息:

l: 表示扫描线的左端x坐标

r:表示扫描线的右端x坐标

h: 表示扫描线的高度

d: 为1或-1,标记扫描线是矩形的上位还是下位边.

我们首先读入所有矩形的信息,每读入一个矩形信息我们就更新两条扫描线,并且把矩形的两个端点x坐标放入X[MAXN]数组中,

然后我们对node和X都排序,node按h值从小到大排序.

X按从小到大排序.

然后我们在X的本地数组内,对X去重,并且用k表示一共有多少个X.

当我们需要找到第i个区域的两端点坐标时,只需要X[i]和X]i+1].

 

线段树维护cnt(根本信息)和sum两个信息,其中sum为double,cnt为int型.

cnt: >=0时表示本节点控制的区域内下位边个数-上位边个数的结果.如果==-1时,表示本节点左右子树的上下位边数不一致.

sum: 本节点控制的区域内cnt值不为0的区域总长度.

 

线段树操作:

PushDown(i,l,r):如果cnt!=-1,那么下放cnt信息,并更新子节点的sum信息.

PushUp(i,l,r): 根据子节点的cnt值和sum值更新父节点的cnt和sum值.

update(ql,qr,v,i,l,r): 使得[ql,qr]与[l,r]区间的公共部分cnt值+v.

如果ql<=l && r<=qr 且 cnt[i]!=-1的话,直接更新并return

否则先PushDown

在一次递归更新左右儿子

最后PushUp.

#include 
  
    #include 
   
     #include 
    
      #include 
     
       #include 
      
        #include 
       
         #include 
        
          #include 
          #include 
          
            #define INF using namespace std; const int MAX=200+10; int mark[MAX<<2];//记录某个区间的下底边个数 double sum[MAX<<2];//记录某个区间的下底边总长度 double hash[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树 //以横坐标作为线段(区间),对横坐标线段进行扫描 //扫描的作用是每次更新下底边总长度和下底边个数,增加新面积 struct seg{//线段 double l,r,h; int d; seg(){} seg(double x1,double x2,double H,int c):l(x1),r(x2),h(H),d(c){} bool operator<(const seg &a)const{ return h 
           
             >1; if(L<=mid)Update(L,R,d,n<<1,left,mid); if(R>mid)Update(L,R,d,n<<1|1,mid+1,right); Upfather(n,left,right); } int search(double key,double* x,int n){ int left=0,right=n-1; while(left<=right){ int mid=left+right>>1; if(x[mid] == key)return mid; if(x[mid]>key)right=mid-1; else left=mid+1; } return -1; } int main(){ int n,num=0; double x1,x2,y1,y2; while(cin>>n,n){ int k=0; for(int i=0;i 
            
              >x1>>y1>>x2>>y2; hash[k]=x1; s[k++]=seg(x1,x2,y1,1); hash[k]=x2; s[k++]=seg(x1,x2,y2,-1); } sort(hash,hash+k); sort(s,s+k); int m=1; for(int i=1;i 
              
             
            
           
         
        
       
      
     
    
  

无注释精简版:

#include 
  
    using namespace std; #define lson i<<1,l,m #define rson i<<1|1,m+1,r const int maxn=222; double x[maxn]; struct node { double l,r,h;//左右坐标,高度 int d;//标记上位边还是下位边 node() {} node(double l,double r,double h,int d):l(l),r(r),h(h),d(d) {} bool operator < (const node &a)const { return h 
   
     =r) { cnt[i]+=v; pushup(i,l,r); return ; } int m=(l+r)>>1; if(ql<=m)update(ql,qr,v,lson); if(qr>m)update(ql,qr,v,rson); pushup(i,l,r); } int main() { int q; int kase=0; while(cin>>q&&q) { memset(cnt,0,sizeof(cnt));//相当于build memset(sum,0,sizeof(sum));//相当于build int n=0,m=0; for(int i=1; i<=q; i++) { double x1,y1,x2,y2; scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); x[++n]=x1; x[++n]=x2; line[++m]=node(x1,x2,y1,1); line[++m]=node(x1,x2,y2,-1); } sort(x+1,x+1+n); sort(line+1,line+1+m); int k=1; /* for(int i=2;i<=n;i++)//去重 { if(x[i]!=x[i-1])x[++k]=x[i]; }*/ k=unique(x+1,x+n+1)-x-1;//直接用STL中的unique函数。 double ans=0.0; for(int i=1; i 
     
    
  

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178273.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午5:39
下一篇 2026年3月26日 下午5:39


相关推荐

  • 安装 arm nginx aarch64[通俗易懂]

    安装 arm nginx aarch64[通俗易懂]网上搜了一大堆通过编译方式安装nginx的方法其实nginx提供了aarch64版本的nginxhttp://nginx.org/en/linux_packages.html2中选择,使用yum安装软件的选择centos的方法,apt安装软件的选择debain或者ubuntu的方法添加软件源地址,然后就可以快乐的玩耍了不管是yum还是apt安装后nginx都不会自己启动需要systemctlstartnginx手动启动nginx服务

    2022年10月16日
    3
  • ffprobe使用详解

    ffprobe使用详解自己应用的内容是 nbsp nbsp nbsp 查看 MP4 文件中 mdatbox 中的 h264 每个 slice 的内容 二进制 nbsp nbsp 命令为 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp ffprobe exe nbsp show packets nbsp show data nbsp nbsp input mp4 nbsp nbsp gt nbsp c text11 txt nbsp nbsp nbsp nbsp nbsp 所有的 slice 如果想获得 I 帧就用 show frames

    2026年3月20日
    2
  • 轻量级神经网络发展_宽度神经网络

    轻量级神经网络发展_宽度神经网络文章目录轻量级神经网络——shuffleNetshuffleNet1逐点分组卷积(Pointwisegroupconvolution)✨✨✨通道重排(channelshuffle)✨✨✨shuffleNetUnit✨✨✨shuffleNet1的网络结果和效果轻量级神经网络——shuffleNetshuffleNet1  在之前,已经讨论过一种轻量级神经网络——MobileNet,文中对MobileNet的三个版本都做了详细的介绍,读此篇之前,建议先了解MobileNet,特别是要对其中的深度可

    2025年10月9日
    6
  • 心灵鸡汤【5】

    心灵鸡汤【5】

    2021年8月20日
    58
  • nginx设置301永久重定向

    nginx设置301永久重定向

    2021年10月27日
    42
  • gtest初识_tests strength

    gtest初识_tests strengthgtest初识总结本文以结合gtestgithub内容进行学习gtest。gtestgithub地址gtest编译g++xx.cppxx.h-lgtest-lpthread-omaingtest编写创建测试的一个简易的步骤:1.使用TEST()宏来定义和命名测试函数,这些是不返回值的普通C++函数。2.在此函数中,与要包含的任何有效C++语句一起使用各种g…

    2026年4月18日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号