扫描线

计算几何系列 —— 扫描线算法 显然,只要一条线段被覆盖,那么它肯定被图形所截。所以,整个问题就转化为了一个区间查询问题,即:每次将当前扫描线扫到的边对应的信息按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积。这就可以用线段树来实现了(线段树:顾名思义,就是树上的结点为一条条线段~),接下来我们来简单看一下模拟过程: 阅读详情

扫描线是一种用来解决许多矩形面积/周长并的问题


下面先来说矩形面积并

现在我们有这样四个矩形,我们要求他们的面积并,怎么求呢?我们就需要利用到扫描线算法

我们考虑把每个矩形拆成两条直线,就像这样

然后我们发现我们要求的面积被扫描线分成了这样的几个部分

 

我们可以考虑使用线段树来维护整体被覆盖的长度,同时把扫描线拆成+1和-1两种

每次计算的答案就应该是线段树中被覆盖的长度\times (x_i-x_{i-1})

但是线段树怎么维护一段区间被覆盖的长度是多少呢?

这个东西其实利用标记永久化的线段树更加方便

我们给每个节点开两个变量tag和sum分别表示这个点被完整覆盖了几次和答案

那么每次更新的时候我们就找到这些区间,让他们的tag加一,然后pushup就可以了

程序写出来大概是这样的

void pushup(int u){
    if(seg[u].tag)seg[u].sum=b[seg[u].r+1]-b[seg[u].l];
    else seg[u].sum=seg[lc].sum+seg[rc].sum;
}

void update(int u,int l,int r,int k){
    if(seg[u].l>=l&&seg[u].r<=r){seg[u].tag+=k,pushup(u);return;}
    int mid=seg[u].l+seg[u].r>>1;
    if(l<=mid)update(lc,l,r,k);
    if(r>mid)update(rc,l,r,k);
    pushup(u);
}

注意因为我们离散化过了,所以应该是之前的值

但是我们会发现这个东西如果我们叶子节点存储的是一个点的话+1-1的判断非常麻烦,所以为了方便,我们应该让每个节点储存的是(l,r)之间的一段区间,这样就不用考虑+1-1的问题了,那么我们的线段树也要相应的进行调整

还有一个问题,如果我们修改的时候修改到了叶子节点,我们之前的程序仍然需要执行一次pushup,可能会访问到更深的地方从而造成RE(在洛谷上是WA),所以有两种解决方法,一是开十六倍空间,二是特判叶子节点的情况

那么最后我们的代码写出来是这个样子滴

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=2e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int b[N];
ll ans;

struct scan{
    int x,y1,y2,opt;
    bool operator < (const scan &cmp)const{
        if(x!=cmp.x)return x<cmp.x;
        return opt>cmp.opt;
    }
}a[N];

struct segment_tree{
    int l,r,sum,tag;
}seg[N<<2];

# define lc (u<<1)
# define rc (u<<1|1)

void pushup(int u){
    if(seg[u].r==seg[u].l+1){
        if(seg[u].tag)seg[u].sum=b[seg[u].r]-b[seg[u].l];
        else seg[u].sum=0;
    }
    else{
        if(seg[u].tag)seg[u].sum=b[seg[u].r]-b[seg[u].l];
        else seg[u].sum=seg[lc].sum+seg[rc].sum;
    }
}

void build(int u,int l,int r){
    seg[u].l=l,seg[u].r=r;
    if(r==l+1)return;
    int mid=l+r>>1;
    build(lc,l,mid);
    build(rc,mid,r);
}

void update(int u,int l,int r,int k){
    if(seg[u].l>=l&&seg[u].r<=r){seg[u].tag+=k,pushup(u);return;}
    int mid=seg[u].l+seg[u].r>>1;
    if(l<mid)update(lc,l,r,k);
    if(r>mid)update(rc,l,r,k);
    pushup(u);
}

signed main()
{
    read(n);
    Rep(i,1,n){
        int x1,x2,y1,y2;
        read(x1),read(y1),read(x2),read(y2);
        b[i]=y1,b[i+n]=y2;
        a[i]=(scan){x1,y1,y2,1};
        a[i+n]=(scan){x2,y1,y2,-1};
    }
    sort(a+1,a+2*n+1);
    sort(b+1,b+2*n+1);
    int sz=unique(b+1,b+2*n+1)-b-1;
    Rep(i,1,2*n){
        a[i].y1=lower_bound(b+1,b+sz+1,a[i].y1)-b;
        a[i].y2=lower_bound(b+1,b+sz+1,a[i].y2)-b;
    }
    build(1,1,sz);
    Rep(i,1,2*n){
        ans+=1ll*seg[1].sum*(a[i].x-a[i-1].x);
        update(1,a[i].y1,a[i].y2,a[i].opt);
    }
    printf("%lld\n",ans);
    return 0;
}

如果大家习惯写标记下传的线段树的话可能不太习惯,但是这道题我们会发现标记永久化的线段树显然更佳好写一些

但是我们注意一下,我们在这道题目中写的是只查询线段树中1号节点的答案,那么这个时候我们是不需要任何特别的操作的,但是我们仔细观察这个做法我们就会发现如果查询的话还按之前的查询方法是会出现问题的,因为一个点下面的点并没有被标记过

所以这个时候如果是区间查询的话我们就需要标记一下路径上是否有被标记过的点,如果有就输出这一段的b_r-b_l

也许将来我会专门写一个标记永久化的博客我回头再说吧,但是上面说的其实都是给下面周长并做铺垫


2020.4.14 20:55 作业写不完了,明天再说周长并吧


2020.4.15 

下面我们来说一下周长并

现在有四个矩形,我们需要求他们的周长并

我们先考虑竖着的怎么计算

我们观察一下竖着的答案是哪一部分

应该是标红的部分

顺着面积并的做法,我们还是把矩形拆成+1-1两种边,仔细观察上面的那张图我们就会发现

对于+1的边,我们的答案就是这条边对应的区间之前没有被覆盖过的长度

对于-1的边,我们的答案就是这条边删掉之后对应的区间没有被覆盖过的长度

但是这个东西貌似不太好维护,而且我们想直接粘上面那道题的代码,怎么办呢?

我们可以把问题转化成区间长度减去被覆盖过至少一次的长度,这样我们就可以直接粘上面的线段树代码了

但是!!!这里涉及到了区间查询的问题,所以我们查询的时候要修改一下正常的查询

横着的边就再做一遍就好了

但是代码想写好看还真的挺难的

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int b[2][N],szx,szy;
int ans;

struct ScanLine{
    int x,y1,y2,opt;
    bool operator < (const ScanLine &cmp)const{
        if(x!=cmp.x)return x<cmp.x;
        return opt>cmp.opt;
    }
}a[2][N];

struct segment_tree{
    int l,r,tag,sum,siz;
}seg[N<<2];

# define lc (u<<1)
# define rc (u<<1|1)

void pushup(int u){
    if(seg[u].r==seg[u].l+1){
        if(seg[u].tag)seg[u].sum=seg[u].siz;
        else seg[u].sum=0;
    }
    else{
        if(seg[u].tag)seg[u].sum=seg[u].siz;
        else seg[u].sum=seg[lc].sum+seg[rc].sum;
    }
}

void build(int u,int l,int r,int which){
    seg[u].l=l,seg[u].r=r;
    seg[u].tag=seg[u].sum=0;
    seg[u].siz=b[which][r]-b[which][l];
    if(r==l+1)return;
    int mid=l+r>>1;
    build(lc,l,mid,which);
    build(rc,mid,r,which);
}

void update(int u,int l,int r,int k){
    if(seg[u].l>=l&&seg[u].r<=r){seg[u].tag+=k,pushup(u);return;}
    int mid=seg[u].l+seg[u].r>>1;
    if(l<mid)update(lc,l,r,k);
    if(r>mid)update(rc,l,r,k);
    pushup(u);
}

int query(int u,int l,int r,bool down){
    if(seg[u].l>=l&&seg[u].r<=r)return down?seg[u].siz:seg[u].sum;
    int mid=seg[u].l+seg[u].r>>1;
    int res=0;
    if(l<mid)res+=query(lc,l,r,down|seg[u].tag);
    if(r>mid)res+=query(rc,l,r,down|seg[u].tag);
    return res;
}

int main()
{
    read(n);
    Rep(i,1,n){
        int x1,x2,y1,y2;
        read(x1),read(y1),read(x2),read(y2);
        a[0][i]=(ScanLine){x1,y1,y2,1};
        a[0][i+n]=(ScanLine){x2,y1,y2,-1};
        a[1][i]=(ScanLine){y1,x1,x2,1};
        a[1][i+n]=(ScanLine){y2,x1,x2,-1};
        b[0][i]=y1,b[0][i+n]=y2;
        b[1][i]=x1,b[1][i+n]=x2;
    }
    sort(a[0]+1,a[0]+2*n+1);
    sort(a[1]+1,a[1]+2*n+1);
    sort(b[0]+1,b[0]+2*n+1);
    sort(b[1]+1,b[1]+2*n+1);
    szy=unique(b[0]+1,b[0]+2*n+1)-b[0]-1;
    szx=unique(b[1]+1,b[1]+2*n+1)-b[1]-1;
    Rep(i,1,2*n){
        a[0][i].y1=lower_bound(b[0]+1,b[0]+szy+1,a[0][i].y1)-b[0];
        a[0][i].y2=lower_bound(b[0]+1,b[0]+szy+1,a[0][i].y2)-b[0];
        a[1][i].y1=lower_bound(b[1]+1,b[1]+szx+1,a[1][i].y1)-b[1];
        a[1][i].y2=lower_bound(b[1]+1,b[1]+szx+1,a[1][i].y2)-b[1];
    }
    build(1,1,szy,0);
    Rep(i,1,2*n){
        if(a[0][i].opt==1){
            ans+=b[0][a[0][i].y2]-b[0][a[0][i].y1]-query(1,a[0][i].y1,a[0][i].y2,0);
            update(1,a[0][i].y1,a[0][i].y2,a[0][i].opt);
        }
        else{
            update(1,a[0][i].y1,a[0][i].y2,a[0][i].opt);
            ans+=b[0][a[0][i].y2]-b[0][a[0][i].y1]-query(1,a[0][i].y1,a[0][i].y2,0);
        }
    } 
    build(1,1,szx,1);
    Rep(i,1,2*n){
        if(a[1][i].opt==1){
            ans+=b[1][a[1][i].y2]-b[1][a[1][i].y1]-query(1,a[1][i].y1,a[1][i].y2,0);
            update(1,a[1][i].y1,a[1][i].y2,a[1][i].opt);
        }
        else{
            update(1,a[1][i].y1,a[1][i].y2,a[1][i].opt);
            ans+=b[1][a[1][i].y2]-b[1][a[1][i].y1]-query(1,a[1][i].y1,a[1][i].y2,0);
        }
    }  
    printf("%d\n",ans);
    return 0;
}

 

扫描线填充多边形算法详解与代码 扫描线填充多边形算法详解与代码首先给出已知信息:多边形结构体里面包含三个信息:顶点个数,顶点和颜色class MyPolygon { public: int m_VerticeNumber; CPoint m_Vertex[50]; COLORREF m_LineColor; }思路:   找到多边形的最小y值和最大y值,然后用这个范围内的每一条水平线与多边形相交,通过 阅读详情

相关推荐

线段树应用——扫描线

扫描线(难的批爆)

Lucas_FC_的博客 2782

【算法】-扫描线

在计算几何中,扫描线算法(scan line algorithm)一般用来解决几何图形的面积交并,周长交并问题,扫描线算法的核心思想是利用扫描线(通常是水平线或垂直线)在几何空间中“扫描”对象,以确定哪些对象与扫描线相交。下面我们就来通过求矩形的面积并来介绍扫描线算法。先来看看怎么求下面图形的面积并:传统算法是两个矩形面积相加减去重合的面积:但是这样算非常的耗费时间,因为每个矩形都需要两两配对,查看互相之间是否有交集。那么我们接下来就想着把矩形分成三部分:于是现在就变成了。

knoci的博客 7399

多边形颜色填充-X扫描线算法

在光栅显示器上选定一系列的点,将点按顺序连接起来形成一个封闭的多边形,并对多边形内的像素点进行颜色填充。本文主要介绍X扫描线算法以实现多边形内目标像素点的确定。 一.X扫描线算法 首先确定该多边形的和,通过y变量以为起点,以为终点,每次步长为1,与多边形相交求得交点,在交点的区间内所得的整数即为所求像素点。例如下图,,扫描线y=4与多边形交于P1、P2、P3、P4点,4个交点的x坐标分别为3.20、5.33、6.50、8.43,因此所求的目标像素点分别为P1和P2区间[4,4]、[5,4],P3和P4区

SX2101111的博客 5374

扫描线(板子整理) 矩形面积并与矩形周长并

前置知识:离散化,线段树基础即可,难度不大,重在思维扫描线,矩形面积并(洛谷)为了归并区间中的关系(每一段都是连起来的,所以要右端点偏移映射,后面在代码中体现)与常规维护懒标记不同,这里是向上维护的,最后返回一个tree.len[1],就是根节点的值,就是答案即每一段区间右边那个位置(在离散化后的数组中)右偏了,直接体现在操作中而不是结构中。

why_not_fly的博客 1224

扫描线算法笔记(C++)

本文介绍扫描线算法, 通过图解和代码帮助读者理解.

一些C++相关的技术笔记或者教程. 1727

【2024最新】C++扫描线算法介绍+实战例题

扫描线算法的一个介绍,里面有资料很少的一维扫描线,也有丰富的个人试错经验,干货满满。快来看ヾ(•ω•`)o

YLCHUP的博客 1714

线段树之扫描线(HDU-1542)

线段树之扫描线,主要是为了解决平面矩阵并以及空间立体交的问题。

月巴虫丰博客 618

扫描线法填充多边形

原理 如下图所示多边形: 简述 直线y=1,2,3……8顺序扫描多边形,以直线y=3为例,它与多边形的边有4个交点,将这4个交点的x坐标保存下来,两两之间画线,也就是(A,B)(C,D)之间画线。对于每一个y都是两两之间画线。现在考虑直线y=2,它与多边形有3个交点,很显然是F,G之间画线,那么p1怎么处理呢?可以观察到p1是多边形边的一个端点,是两条边的交点,因此,可以选择将p1看

小小鸟 3721

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

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

20164225的博客 2万+

多边形区域填充算法--扫描线种子填充算法

多边形区域填充算法--扫描线种子填充算法

hffyfdt的博客 2063

【运动规划算法项目实战】全覆盖分区算法

全覆盖规划在机器人学领域具有重要意义,其应用场景涵盖了自动农场、扫地机器人、扫描监视无人机等多个领域。该问题的核心目标是使机器人能够从指定起点出发,规划一条避开障碍物、同时路径尽可能短的轨迹,从而实现对特定区域的全面覆盖。在实际应用中,全覆盖规划的成功实现对于提高机器人在各类环境中的工作效率至关重要。这一问题的解决涉及到高效的路径规划和规划区域分解技术。其中,梯形单元分解算法是一种常用分区的技术,过将地图上需要规划的区域进行分区,采用梯形分解的方式,可以更好地应对复杂环境。全覆盖算法规划流程。

xiewf的博客 1万+

扫描线算法

背景 之前看到洛谷管理员大佬发明了个二次分块,然后就想学学,发现扫描线是个前置知识,于是来肝这个算法了,发现其实不难。 进入正题 扫描线一个很经典的例题:在坐标轴上有若干个矩形,问他们覆盖的面积总和。 因为他们覆盖的面积有重复,于是就用到了神奇的扫描线算法。 假设有两个矩形,如图: 先来看一道例题:题目大意, ...

a_forever_dream的博客 2万+

点云提取扫描线

1.扫描线提取原理 目前车载LiDAR系统搭载的激光扫描仪主要是线性扫描,获得的扫描点在目标上按扫描线排列。在同一扫描线中,系统记录的连续激光脚点的扫描角度差值为固定值(一般为激光扫描仪的角度分辨率)。在一个完整的扫描周期中,若扫描视场角为顶部天空,会出现无激光脚点返回的情况。此时当前扫描线的最后一个点和下一条扫描线的起始点的扫描角度有一个非规律的阶跃。同理,因为车载激光点云的连续性,当扫描视角为...

独学而无友,则孤陋而寡闻 4424

c++实现扫描线种子填充算法

今天我们来介绍一种利用堆栈实现的填充算法,这种算法相比于直接使用递归实现的填充算法(如内点表示的四连通种子填充算法)来说,它需要的堆栈大小不需要那么庞大,下面是具体实现: ...

LW的技术小筑 6342

DMTF相关协议(一):Redfish和SMBIOS

Redfish 是一种管理标准,使用超媒体 RESTful 接口中的数据模型表示。该模型以标准的、机器可读的模式表示,消息的有效负载以 JSON 表示。该协议本身利用了 OData v4。由于它是一个超媒体 API,因此能够通过一致的接口表示各种实现。它具有管理数据中心资源、处理事件、长时间任务和发现的机制。

weixin_44032634的博客 2264

糖果【贪心】

【问题描述】 有N(1≤N <100,000)N(1≤N~<100,000)N(1≤N <100,000)种不同类型的糖果,每种糖果数量无限多。第i种糖果每颗的价格是Pi(1≤Pi≤1018),有Ci(1≤Ci≤1018)P_i(1≤P_i≤10^{18}),有C_i (1≤C_i≤10^{18)}Pi​(1≤Pi​≤1018),有Ci​(1≤Ci​≤1018)头牛想吃这种糖果。 FarmerJohnFarmer JohnFarmerJohn拥有金钱BBB元(1≤B≤1

算法小猪的博客 794

多边形扫描线填充算法

按扫描线顺序,计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的象素,即完成填充工作。对于一条扫描线填充过程可以分为四个步骤:1. 求交:计算扫描线与多边形各边的交点;2.排序:把所有交点按x值递增顺序排序;3. 配对:第一个与第二个,第三个与第四个等等;每对交点代表扫描线与多边形的一个相交区间,4.着色:把相交区间内的象素置成多边形颜色,把相交区间外的象素置成背景色。

2401_85042349的博客 2287

微信文件自动归档!手把手教你用OpenClaw搭个自己的“数字员工”,告别重复劳动

很多人说“AI是未来”,但未来不是靠喊口号的。OpenClaw的真正价值,是让你把重复劳动交出去,把时间留给真正需要创造力的事。这事真能干,而且不难。下载OpenClaw(GitHub仓库按我写的步骤跑一遍遇到坑,评论区留言——我亲自帮你看作者后记:上周五,我让AI自动归档了文件后,终于在5点准时下班。回家路上,我给它发了条消息:“干得漂亮”。它没回,但我知道——它在默默等我的下一条指令。

2601_94871597的博客 412

扫描线zbuffer消隐算法

本片blog阐述了图形学中扫描线缓冲器消隐算法的原理和C++实现。

a_big_pig的专栏 2万+

ros_gz_bridge__镜像__readme

Run。

1113

SI案例分享--考虑ESD保护二极管的差分线阻抗仿真

PCB设计中的cut out参考面早已是SI评估中常用的阻抗优化方式,通过合理的仿真建模,可以提供合适的设计参考,并保持与测试结果的高度一致性。

一只豌豆象的博客 1436

扫描线讲解

扫描线保姆级教学!欢迎诸位神犇~

JX-ashang的博客 1082

破盾实录|Vueling航空阿卡迈防护逆向:从503挑战到Cookie生成的全流程拆解

摘要:文章分享了作者在爬取西班牙伏林航空(Vueling)机票数据时,遭遇阿卡迈(Akamai)防护系统的实战经历。起初简单的请求被拦截返回503错误,分析发现阿卡迈通过混淆JS代码实施"挑战-验证"机制,需模拟真实浏览器环境才能获取有效Cookie。作者详细拆解了逆向过程:从抓包分析JS挑战流程,到破解代码混淆和控制流平坦化,最终用Python+Pyppeteer成功模拟浏览器特征,突破防护获取数据。整个过程涉及JS反混淆、环境检测绕过和Cookie生成验证等关键技术点,为类似防护系统

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1642
上一篇: 中国剩余定理
下一篇: [HAOI2012] 高速公路
NephrenRuqInsania
博客等级 码龄8年 20粉丝 115原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值