• 【扫描线】学习笔记


    【模板】扫描线

    题目描述

    求 n 个矩形的面积并。

    输入格式

    第一行一个正整数 nn。

    接下来 nn 行每行四个非负整数 x_1, y_1, x_2, y_2x1,y1,x2,y2,表示一个矩形的左下角坐标为 (x_1, y_1)(x1,y1),右上角坐标为 (x_2, y_2)(x2,y2)。

    输出格式

    一行一个正整数,表示 nn 个矩形的并集覆盖的总面积。

    输入输出样例

    输入 #1
    2
    100 100 200 200
    150 150 250 255
    
    输出 #1
    18000
    

    说明/提示

    对于 20\%20% 的数据,1 le n le 10001n1000。

    对于 100\%100% 的数据,1 le n le 10^5, 0 le x_1 < x_2 le 10^9, 0 le y_1 < y_2 le 10^91n105,0x1<x2109,0y1<y2109。

    扫描线的思想主要思想就是分割图形。

    扫描线的方向没有规定,这里用从左到有说明扫描线是如何分割图形的。

    我们可以将一个矩形转化成两条边,及左边和右边平行y轴的两条边。

    一条边到另一条边的距离乘边的长度,就是这个矩形的面积。

    只需要处理这每个矩形的两条边对扫描线长度取值的影响即可。

    将所有的边按x的值排列,这样我们可以按边的编号从大到小一个一个扫描。

    当扫描到边i,如果是矩形左边的边,那么这个矩形对面积的贡献就开始了。

    如果没有其他矩形(边)的贡献,那么这条边将会矩形并的面积产生 len*( x(i+1)-x(i) ),

    len是i线段的长,x(i+1)-x(i)是i到下一条线段的长。

    这是显然的,但如何题目要求的是多个矩形的共同产生的影响。

    如何记录他们的影响?

    如果是左边(y1,y2),那么我们让区间(y1,y2)出现的次数+1,(有一个新矩形出现)

    如果是右边(y1,y2),那么我们让区间(y1,y2)出现的次数-1。(这个矩形已经扫完了)

    如果一个区间出现的cnt>=1,则这个区间对扫描线贡献(y2-y1)的长度(区间长)。

    否则他贡献的长度为他的两个自区间贡献的长度。

    那么我们只需要对这条扫描线建立一个线段树,维护他的cnt和len即可。

    由于笔者之前写的段树几乎都是点数,没有区间树。

    也鉴于智商问题,刚开始对区间树的一些操作有一些排斥,所以花了很长时间才理解。

    基于线段树,我们只要先对每条线段记录y1,y2,和val,val的取值是1或-1。

    然后枚举每条边,change一下他们的cnt和len。

    这当前扫描线截取的长度为tree[1].len。用这个len乘i到下一条边的x差,就是这一次割出来的面积。

    下面讲讲区间树和点数的一下小差别。

    比如说,我们要修改[5,8]。

    那么[1,5]和[8,10]与它有没有交集?

    显然,对于点集[1,5],[8,10]是有的,而区间[1,5],[8,10]和[5,8]是没有交集的。

    那该这么处理区间树的边界问题。

    刚开始对于树上每个点,都设置一个l和r。

    但我们实际处理时,这个节点管辖的区间是[l,r+1]

    假如我们要处理的区间为[y1,y2],那么我们实际去找范围在[y1,y2-1]范围的点(线段树实质是点树)。

    这样一波操作, 我们避免了在处理边界时,引入端点相同,却没有交点的区间。

    还有不要忘记离散化。

    贴上代码附上简洁的注释。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ls (k<<1)
    #define rs (k<<1|1)
    #define ll long long
    using namespace std;
    const ll N=1e6;
    ll n,cnt,ans;
    ll a[N<<4];//空间还是大一点好。~惨痛的教训~
    struct node{
        ll l,r,len,cnt;
    }tree[N<<4];
    struct node1{
        ll x,y1,y2,flag;
        bool operator < (const node1 &temp) const{
            return x<temp.x;
        }
    }seg[N<<4];
    inline void read(ll &x){
        x=0;
        char ch=getchar();
        while(ch<'0'||ch>'9') ch=getchar();
        while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    }
    inline void build(ll k,ll x,ll y){
        tree[k].l=x,tree[k].r=y;
        if(x==y) return;
        ll mid=x+y>>1;
        build(ls,x,mid);
        build(rs,mid+1,y);
    }
    inline void updata(ll k){
        if(tree[k].cnt) tree[k].len=a[tree[k].r+1]-a[tree[k].l];//cnt>=1,len为区间长。
        else tree[k].len=tree[ls].len+tree[rs].len;//否则为子区间len和。
    }
    inline void change(ll k,ll x,ll y,ll l,ll r,ll val){
        if(x>=l&&y+1<=r){//实际处理的[l,r],我们找[l,r-1]
            tree[k].cnt+=val;
            updata(k);
            return;
        }
        if(x>=r||y<l) return;
    //左端点如果等,那也没有交集。而右端点可以等。
    //举个例子,当我们要找真实区间[l,r]为[3,5],若[x,y]为[5,8],没有交集,若为[3,3](对于真实区间为[3,4])有交集。 ll mid
    =x+y>>1; change(ls,x,mid,l,r,val); change(rs,mid+1,y,l,r,val); updata(k);//由下到上大updata会比较好实现。 } int main() { ll i,j,x1,x2,y1,y2; read(n); for(i=1;i<=n;i++){ read(x1),read(y1),read(x2),read(y2); a[++cnt]=y1; a[++cnt]=y2;//离散化数组。 seg[(i<<1)-1].x=x1; seg[(i<<1)].x=x2; seg[(i<<1)-1].y1=seg[i<<1].y1=y1; seg[(i<<1)-1].y2=seg[i<<1].y2=y2; seg[(i<<1)-1].flag=1; seg[(i<<1)].flag=-1;
        //左边val=1,右边val=-1。 } sort(a
    +1,a+cnt+1); sort(seg+1,seg+(n<<1)+1); cnt=unique(a+1,a+1+cnt)-a-1; build(1,1,cnt); for(i=1;i<=n<<1;i++){ seg[i].y1=lower_bound(a+1,a+1+cnt,seg[i].y1)-a; seg[i].y2=lower_bound(a+1,a+1+cnt,seg[i].y2)-a;//找到每个线段在a中的映射。 } for(i=1;i<=n<<1;i++){ y1=seg[i].y1,y2=seg[i].y2; change(1,1,cnt,y1,y2,seg[i].flag); ans+=(seg[i+1].x-seg[i].x)*tree[1].len;//分割图形,更新答案。 } printf("%lld",ans); }
  • 相关阅读:
    基本数据类型转换
    java8新增的日期时间包
    算法之冒泡排序
    基本数据类型
    spring入门
    JiuDuOj——1020
    JiuDuOj——1051
    2015.11.26——Accelerated C++
    POJ2681——求字符串长度
    POJ1017——装箱问题
  • 原文地址:https://www.cnblogs.com/quitter/p/11694287.html
Copyright © 2020-2023  润新知