1228. 油漆面积

Powered by:NEFU AB-IN

Link

1228. 油漆面积

  • 题意

    X星球的一批考古机器人正在一片废墟上考古。
    该区域的地面坚硬如石、平整如镜。
    管理人员为方便,建立了标准的直角坐标系。
    每个机器人都各有特长、身怀绝技。
    它们感兴趣的内容也不相同。
    经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。
    矩形的表示格式为 (x1,y1,x2,y2),代表矩形的两个对角点坐标。
    为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。
    小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆
    其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
    注意,各个矩形间可能重叠。

  • 思路

    离散线段树 完成扫描线算法
    之前写过这个题的题解,用的是y总讲的扫描线的模板,但每次都记不住…
    这次是用的董老师的模板,直接套用离散线段树,不用二分find,直接在build的时候就放入y坐标值了,非常好用!!这是视频链接 link


    概念解释

    扫描线: 就是一根线,从左往右扫,以每个矩形的左右边为界,可以把n个矩形组成的区域分割成2n-1个区块,每个区块的宽就是扫过的距离,每个区块的有效高度一直在变化。
    有效高度: 就是区块内矩形的高度的并,每当扫到一个矩形的左边时,就加入该矩形高的贡献;而当扫到该矩形的右边时,就减去该矩形高的贡献。
    线段树的作用:有效高度不断变化,可以通过线段树维护区间的修改。但是,所给矩形的Y坐标“值域大(109)数量少(105)".需要离散化。可以使用离散线段树直接维护Y坐标。


    算法流程

    1. 扫描线切分区块
    2. 离散线段树维护Y坐标和区块的有效高度,自顶向下分裂区间,自底向上拼凑有效高度(上面概念介绍有说),标记加入或减去一个矩形对有效高度的贡献
    3. 矩形面积并= sum(区块宽度×区块有效高度)

    下面是董老师视频中的截图,对比了普通线段树和离散线段树的区别
    img

    下面我会放不带注释带注释的代码,建议要真正理解思想

  • 代码

    不带注释

    /*
    * @Author: NEFU AB-IN
    * @Date: 2023-03-28 20:04:18
    * @FilePath: \Acwing\1228\1228.cpp
    * @LastEditTime: 2023-03-28 20:50:10
    */
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    
    #define SZ(X) ((int)(X).size())
    #define ALL(X) (X).begin(), (X).end()
    #define IOS                                                                                                            \
        ios::sync_with_stdio(false);                                                                                       \
        cin.tie(nullptr);                                                                                                  \
        cout.tie(nullptr)
    #define DEBUG(X) cout << #X << ": " << X << '\n'
    typedef pair<int, int> PII;
    #define ls p << 1
    #define rs p << 1 | 1
    const int N = 1e5 + 10, INF = 0x3f3f3f3f;
    
    struct Tree
    {
          
          
        int l, r, len, cnt;
    } tr[N << 3];
    
    struct Line
    {
          
          
        int x, y1, y2, flag;
    
        bool operator<(Line &a) const
        {
          
          
            return x < a.x;
        }
    } seg[N];
    
    int n, xs[N];
    
    void pushup(int p)
    {
          
          
        if (tr[p].cnt)
            tr[p].len = tr[p].r - tr[p].l;
        else
            tr[p].len = tr[ls].len + tr[rs].len;
    }
    
    void build(int p, int l, int r)
    {
          
          
        tr[p] = {
          
          xs[l], xs[r], 0, 0};
        if (r == l + 1)
            return;
        int mid = l + r >> 1;
        build(ls, l, mid);
        build(rs, mid, r);
    }
    
    void update(int p, int L, int R, int v)
    {
          
          
        if (tr[p].l >= R || tr[p].r <= L)
            return;
        if (tr[p].l >= L && tr[p].r <= R)
        {
          
          
            tr[p].cnt += v;
            pushup(p);
            return;
        }
        update(ls, L, R, v);
        update(rs, L, R, v);
        pushup(p);
    }
    
    signed main()
    {
          
          
        IOS;
        int n;
        cin >> n;
        for (int i = 1; i <= n; ++i)
        {
          
          
            int x1, x2, y1, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            seg[i] = {
          
          x1, y1, y2, 1};
            seg[i + n] = {
          
          x2, y1, y2, -1};
            xs[i] = y1;
            xs[i + n] = y2;
        }
        n *= 2;
    
        sort(seg + 1, seg + 1 + n);
        sort(xs + 1, xs + 1 + n);
    
        build(1, 1, n);
        int ans = 0;
        for (int i = 1; i < n; ++i)
        {
          
          
            update(1, seg[i].y1, seg[i].y2, seg[i].flag);
            ans += (seg[i + 1].x - seg[i].x) * tr[1].len;
        }
        cout << ans << '\n';
        return 0;
    }
    

    带注释

    /*
    * @Author: NEFU AB-IN
    * @Date: 2023-03-28 20:04:18
    * @FilePath: \Acwing\1228\1228.cpp
    * @LastEditTime: 2023-03-28 20:50:10
    */
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    
    #define SZ(X) ((int)(X).size())
    #define ALL(X) (X).begin(), (X).end()
    #define IOS                                                                                                            \
        ios::sync_with_stdio(false);                                                                                       \
        cin.tie(nullptr);                                                                                                  \
        cout.tie(nullptr)
    #define DEBUG(X) cout << #X << ": " << X << '\n'
    typedef pair<int, int> PII;
    #define ls p << 1
    #define rs p << 1 | 1
    const int N = 1e5 + 10, INF = 0x3f3f3f3f; 
    // 题目给的n是1e4, 如果这么开的话会RE,所以要多开十倍!!!比较保险
    
    struct Tree
    {
          
          
        int l, r, len, cnt; // len: 有效高度,cnt: 覆盖次数
    } tr[N << 3]; // 要开8倍空间(这是建立在n是正常开的基础上的,但我们这里n多开了十倍,所以这里也可以开4倍)
    
    struct Line
    {
          
          
        int x, y1, y2, flag; // flag 就代表 矩阵左边的线为1,右边的线为-1
    
        bool operator<(Line &a) const  // 其实这里这么写也可以 bool operator<(Line a) 
        {
          
          
            return x < a.x;
        } // 按x坐标排序
    } seg[N];
    
    int n, xs[N]; // 离散化数组
    
    void pushup(int p) 
    // 个人理解:和线段树的pushup一样,都叫pushup,而且都是往上传
    // 普通线段树的pushup是一个情况,而这个是分为两种情况,所以两个地方(if中和函数最后)都得pushup
    // 本质上是 len属性的两种情况更新
    // 两种情况:一种是有标记,那么就自己处理len;另一种是没标记,那么就要等于子节点的len和
    {
          
          
        if (tr[p].cnt)
            tr[p].len = tr[p].r - tr[p].l;
        else
            tr[p].len = tr[ls].len + tr[rs].len;
    }
    
    void build(int p, int l, int r)
    {
          
          
      // 这就是我之前说的,在build的时候就已经离散化了,也就是这里的l,r已经不是普通线段树的l,r了,已经下标对应到真实的y坐标了,具体可根据上图理解
        tr[p] = {
          
          xs[l], xs[r], 0, 0};
        if (r == l + 1) // 这里也是不同的地方,因为叶子宽度为2
            return;
        int mid = l + r >> 1;
        build(ls, l, mid);
        build(rs, mid, r); // 一定要注意这里是mid,不是mid+1,这里的下标是允许重合的
    }
    
    void update(int p, int L, int R, int v)
    {
          
          
        if (tr[p].l >= R || tr[p].r <= L) // 越界 return,这里也是不同点,也就是我们递归到的区间不在[L, R]中,那就return
            return;
        if (tr[p].l >= L && tr[p].r <= R)
        {
          
          
            tr[p].cnt += v; // 打上标记,v的取值是1或-1,cnt的值就不止1了
            pushup(p); // 这里需要pushup!!! 其实类似于普通线段树的区间修改,这里也要更新结点的属性,但是len没更新,我们是把它的更新放到了pushup中
            return;
        }
        update(ls, L, R, v); // 这里也是不同点,由于设置了越界,每个分支都得进,判断交给越界即可
        update(rs, L, R, v);
        pushup(p); // 最后正常pushup
    }
    
    signed main()
    {
          
          
        IOS;
        int n;
        cin >> n;
        for (int i = 1; i <= n; ++i)
        {
          
          
            int x1, x2, y1, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            seg[i] = {
          
          x1, y1, y2, 1};
            seg[i + n] = {
          
          x2, y1, y2, -1};
            xs[i] = y1;
            xs[i + n] = y2;
        }
        n *= 2; // n的范围扩大一倍,因为两条边界
    
        //有序!
        sort(seg + 1, seg + 1 + n);
        sort(xs + 1, xs + 1 + n); 
    
        build(1, 1, n);
        int ans = 0;
        for (int i = 1; i < n; ++i) // 只需到n-1即可,因为每当遍历一条边时,它右边的面积就算上了,所以最后的就不用了 (当然写成n也可以)
        {
          
          
            // 先更新 再加和
            update(1, seg[i].y1, seg[i].y2, seg[i].flag);
            ans += (seg[i + 1].x - seg[i].x) * tr[1].len;
            // tr[1].len 即当前的有效高度
        }
        cout << ans << '\n';
        return 0;
    }
    

猜你喜欢

转载自blog.csdn.net/qq_45859188/article/details/129826985