首页 > 其他分享 >扫描线

扫描线

时间:2024-08-04 23:28:06浏览次数:8  
标签:rt node mat int seg 扫描线 y1

Abstract

介绍一下扫描线的经典用法。

命名空间还挺好用的。


A-扫描线(模板)

Idea

想象现在有一根线正在从左向右扫描,那么,我们就可以通过纵坐标上区间的覆盖情况去确定扫过的矩形覆盖的面积,区间覆盖情况可以用线段树去维护。实现细节见代码注释。

Code

#include <bits/stdc++.h>
#define int long long
int n;
namespace seg
{
    const int maxn = 1001000;
    int index[maxn * 4];
    // Mat 结构体用于描述矩形的左右两条边
    struct Mat
    {
        // x 表示此边的横坐标
        // y1 y2 表示此边上下端点的纵坐标
        // flag 为 -1 表示这是靠右的边,1 表示这是靠左的边
        int x, y1, y2, flag;
    } mat[maxn * 4];
    bool cmp(Mat a, Mat b)
    {
        return a.x < b.x;
    }
    // Node 节点描述的是区间的覆盖情况
    struct Node
    {
        // l r 记录区间的左右端点
        // sum 记录区间内被覆盖的长度
        int l, r, sum;
    } node[maxn * 4];
    // 懒惰标记,记录这个区间被覆盖的次数
    int lazy[maxn * 4];

// 为了缩减代码量,添加的左右儿子以及中间点的宏定义
#define l(x) (x << 1)
#define r(x) (x << 1 | 1)
#define m(l, r) (l + r >> 1)
    // 用子节点更新父节点
    void pushUp(int rt)
    {
        // 如果懒惰标记存在,那么这个区间肯定全被覆盖了
        if (lazy[rt] > 0)
        {
            node[rt].sum = node[rt].r - node[rt].l;
        }
        else // 否则,用子节点来更新父节点
        {
            node[rt].sum = node[l(rt)].sum + node[r(rt)].sum;
        }
        return;
    }
    // 建树
    void build(int rt, int l, int r)
    {
        // 由于我们考察的对象是区间,所以这里不是 l == r
        if (r - l > 1)
        {
            node[rt].l = index[l];
            node[rt].r = index[r];
            build(l(rt), l, m(l, r));
            build(r(rt), m(l, r), r);
            pushUp(rt);
        }
        else
        {
            node[rt].l = index[l];
            node[rt].r = index[r];
            node[rt].sum = 0;
        }
        return;
    }

    void update(int rt, int l, int r, int flag)
    {
        if (node[rt].l == l && node[rt].r == r)
        {
            lazy[rt] += flag;
            pushUp(rt);
            return;
        }
        else
        {
            if (node[l(rt)].r > l)
            {
                update(l(rt), l, std::min(r, node[l(rt)].r), flag);
            }
            if (node[r(rt)].l < r)
            {
                update(r(rt), std::max(l, node[r(rt)].l), r, flag);
            }
            pushUp(rt);
        }
    }

};

signed main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x1, y1, x2, y2;
        std::cin >> x1 >> y1 >> x2 >> y2;
        // 我们总是规定 x1 >= x2,方便确定左右边
        if (x1 > x2)
        {
            std::swap(x1, x2);
        }
        // 规定 y1 <= y2 ,方便更新线段树
        if (y1 > y2)
        {
            std::swap(y1, y2);
        }
        // 记录矩形
        seg::mat[i].x = x1;
        seg::mat[i].y1 = y1;
        seg::mat[i].y2 = y2;
        seg::mat[i].flag = 1;
        seg::mat[i + n].x = x2;
        seg::mat[i + n].y1 = y1;
        seg::mat[i + n].y2 = y2;
        seg::mat[i + n].flag = -1;
        // 离散化
        seg::index[i] = y1, seg::index[i + n] = y2;
    }
    // 初始化
    std::sort(seg::index + 1, seg::index + 1 + 2 * n);
    std::sort(seg::mat + 1, seg::mat + 1 + 2 * n, seg::cmp);
    seg::build(1, 1, 2 * n);
    memset(seg::lazy, 0, sizeof seg::lazy);
    int ans = 0;
    // 先把第一条边单独拿出来更新
    seg::update(1, seg::mat[1].y1, seg::mat[1].y2, seg::mat[1].flag);
    // 按顺序遍历矩形每一条边
    for (int i = 2; i <= 2 * n; i++)
    {
        // 更新答案
        ans += (seg::mat[i].x - seg::mat[i - 1].x) * seg::node[1].sum;
        // 更新区间覆盖情况
        seg::update(1, seg::mat[i].y1, seg::mat[i].y2, seg::mat[i].flag);
    }
    std::cout << ans;
    return 0;
}

B-奇偶区间(模板进阶)

Idea

这题和上题稍有不同,写了这题,也能加深自己对上一题的认识。

这题的灵魂就在于懒惰标记,注意,这个懒惰标记是没有下传的!我们在更新这个区间的覆盖情况时,如果这个区间的懒惰标记非 0 ,那么这说明它一定正被某一个矩形完全覆盖,只有这个矩形的右边也被扫过,这个懒惰标记才有可能归零!在有懒惰标记的情况下,这个区间自然是被完全覆盖的,那么,如何确定奇偶区间覆盖情况呢?我们可以用子节点的覆盖情况来确定,具体的实现细节见代码注释。

Code

#include <bits/stdc++.h>
#define int long long
int n;
namespace seg
{
    const int maxn = 1001000;
    int index[maxn * 4];
    struct Mat
    {
        int x, y1, y2, flag;
    } mat[maxn * 4];
    bool cmp(Mat a, Mat b)
    {
        return a.x < b.x;
    }

    struct Node
    {
        // sum1 sum2 分别表示被奇/偶数个矩形覆盖的长度
        // 显然的,sum1 + sum2 == sum
        int l, r, sum, sum1, sum2;
    } node[maxn * 4];
    int lazy[maxn * 4];

#define l(x) (x << 1)
#define r(x) (x << 1 | 1)
#define m(l, r) (l + r >> 1)

    void pushUp(int rt)
    {
        if (lazy[rt] > 0)
        {
            node[rt].sum = node[rt].r - node[rt].l;
            if (lazy[rt] & 1)
            {
                // 如果懒惰标记是奇数,那么子区间的奇数覆盖数之和就是父区间的偶数覆盖数
                node[rt].sum2 = node[l(rt)].sum1 + node[r(rt)].sum1;
                node[rt].sum1 = node[rt].sum - node[rt].sum2;
            }
            else
            {
                // 同理的
                node[rt].sum1 = node[l(rt)].sum1 + node[r(rt)].sum1;
                node[rt].sum2 = node[rt].sum - node[rt].sum1;
            }
        }
        else
        {
            node[rt].sum = node[l(rt)].sum + node[r(rt)].sum;
            node[rt].sum1 = node[l(rt)].sum1 + node[r(rt)].sum1;
            node[rt].sum2 = node[l(rt)].sum2 + node[r(rt)].sum2;
        }
        return;
    }

    void build(int rt, int l, int r)
    {
        if (r - l > 1)
        {
            node[rt].l = index[l];
            node[rt].r = index[r];
            build(l(rt), l, m(l, r));
            build(r(rt), m(l, r), r);
            pushUp(rt);
        }
        else
        {
            node[rt].l = index[l];
            node[rt].r = index[r];
            node[rt].sum = 0;
        }
        return;
    }

    void update(int rt, int l, int r, int flag)
    {
        if (node[rt].l == l && node[rt].r == r)
        {
            lazy[rt] += flag;
            pushUp(rt);
            return;
        }
        else
        {
            if (node[l(rt)].r > l)
            {
                update(l(rt), l, std::min(r, node[l(rt)].r), flag);
            }
            if (node[r(rt)].l < r)
            {
                update(r(rt), std::max(l, node[r(rt)].l), r, flag);
            }
            pushUp(rt);
        }
    }

};

signed main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x1, y1, x2, y2;
        std::cin >> x1 >> y1 >> x2 >> y2;
        if (y1 > y2)
        {
            std::swap(y1, y2);
            std::swap(x1, x2);
        }
        seg::mat[i].x = x1;
        seg::mat[i].y1 = y1;
        seg::mat[i].y2 = y2;
        seg::mat[i].flag = 1;
        seg::mat[i + n].x = x2;
        seg::mat[i + n].y1 = y1;
        seg::mat[i + n].y2 = y2;
        seg::mat[i + n].flag = -1;
        seg::index[i] = y1, seg::index[i + n] = y2;
    }
    std::sort(seg::index + 1, seg::index + 1 + 2 * n);
    std::sort(seg::mat + 1, seg::mat + 1 + 2 * n, seg::cmp);
    seg::build(1, 1, 2 * n);
    memset(seg::lazy, 0, sizeof seg::lazy);
    // ans1 表示奇数覆盖区间的面积
    int ans = 0, ans1 = 0;
    seg::update(1, seg::mat[1].y1, seg::mat[1].y2, seg::mat[1].flag);
    for (int i = 2; i <= 2 * n; i++)
    {
        ans += (seg::mat[i].x - seg::mat[i - 1].x) * seg::node[1].sum;
        ans1 += (seg::mat[i].x - seg::mat[i - 1].x) * seg::node[1].sum1;
        seg::update(1, seg::mat[i].y1, seg::mat[i].y2, seg::mat[i].flag);
    }
    std::cout << ans1 << std::endl;
    std::cout << ans - ans1;
    return 0;
}

标签:rt,node,mat,int,seg,扫描线,y1
From: https://www.cnblogs.com/carboxylBase/p/18342392

相关文章

  • 扫描线学习笔记
    前言扫描线思想可以在\(O(n^2)\)的时间复杂度内进行二维平面的计算,运用线段树优化可以在\(O(n\logn)\)的时间复杂度内解决。简介P5490【模板】扫描线以此题为例,介绍扫描线。最直接的想法是将每个正方形的面积先加起来,最后再减去重叠部分,但是代码难度较大,不易于实现。......
  • 扫描线学习笔记
    扫描线是一种算法思想,其特征为将静态\(k\)维问题转化为动态\(k-1\)维问题。动态\(k-1\)维问题往往需要数据结构维护。例题【模板】扫描线题意:求矩形面积并,其中每个举行的四边平行于坐标轴。考虑扫描线,将静态\(2\)维问题转化为动态\(1\)维问题。具体的,考虑按\(......
  • 算法随笔——扫描线
    https://www.acwing.com/solution/content/135911/放个模板先P5490【模板】扫描线#include<bits/stdc++.h>usingnamespacestd;#definelllonglong#defineINF0x3f3f3f3f#definereregister#defineintll#definePIIpair<int,int>intread(){ intf=1,k=0......
  • Cesium雷达扫描线效果
    更多精彩内容尽在dt.sim3d.cn,关注公众号【sky的数孪技术】,技术交流、源码下载请添加VX:digital_twin123源码如下:varviewer=newCesium.Viewer("cesiumContainer");varscene=viewer.scene;varmatGLSL="#defineLlength(c-.1*......
  • 扫描线
    扫描线把题目给的的区间想象成平面直角坐标系上的点.再想象一条直线,按顺序扫描获取信息,维护信息求出矩形面积并:把一个矩形看出两条平行于纵轴的边,一条表示加入,一条表示删除,有很多区间的信息是一样的,用乘法处理,扫描线上的信息最多变动\(2n\)次考虑用线段树......
  • [University CodeSprint 4] Drawing Rectangles (扫描线 + 最小点覆盖)
    [UniversityCodeSprint4]DrawingRectangles扫描线+最小点覆盖题目的形式一看就是扫描线,观察到矩形的并面积\(\le3\times10^5\),所以可以直接把这些位置找出来。这部分的复杂度是\(O(n\logn)\)。然后剩下的部分就是一个经典的最小点覆盖问题。具体的说,构造二分图,左边代......
  • HDU 3642 (扫描线、三维体积相交)
    题意在三维空间中给你n个长方体,求空间中被这些长方体覆盖至少3次以上的区域的总体积。思路这题没给数据组数T的范围,大致看了一下其他人的都是枚举z来做的,所以我这边也是同样的做法转换成二维的扫描线来做,数组ci表示被覆盖i次的区间标记,具体扫描线怎么实现可以看我上篇博客。......
  • 扫描线
    扫描线引入扫描线一般运用在图形上面,它和它的字面意思十分相似,就是一条线在整个图上扫来扫去,它一般被用来解决图形面积、周长,以及二维数点等问题。面积问题例题1:【模板】扫描线想象有一条线从下往上扫,会将整个图像依次扫描。我们只需要计算出每一条矩形(即图中同一颜色的小矩......
  • 扫描线模板
    #include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;constintN=1e6+5;//本模板是从左往右扫的,从下往上扫同理#definels(rt<<1)#definers(rt<<1|1)i64cover[N*8];//存放i节点对应区间覆盖情况的值i64n;i64len[N*8];i64yy[N*2];/......
  • 扫描线
    题目链接https://leetcode.cn/problems/rectangle-area-ii/题目大意题目思路选取连续的x值:(left,right),在这个区间内,沿着x轴的方向扫描,求出所有符合条件的(y1,y2)算出扫描区间的h,结合w*h,算出面积!礼貌拿图,多谢三叶姐(https://leetcode.cn/problems/rectangle-area-ii/so......