Luogu5490 【模板】扫描线(矩形的面积并)

40 篇文章 0 订阅
5 篇文章 0 订阅

原题链接:https://www.luogu.com.cn/problem/P5490

【模板】扫描线

题目描述

n n n 个矩形的面积并。

输入格式

第一行一个正整数 n n n

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

输出格式

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

输入输出样例

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

说明/提示

对于 20 % 20\% 20% 的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 6 , 0 ≤ x 1 < x 2 ≤ 1 0 9 , 0 ≤ y 1 < y 2 ≤ 1 0 9 1 \le n \le 10^6, 0 \le x_1 < x_2 \le 10^9, 0 \le y_1 < y_2 \le 10^9 1n106,0x1<x2109,0y1<y2109

题解

扫描线的模板题,然而原网页上的数据范围写的 n ≤ 1 0 5 n\le 10^5 n105,实际数据范围 1 0 6 10^6 106就™离谱。

扫描线可以说是一种线段树的运用,也可以说是一种思想:在处理二维的问题时,可以先解决其中一维的问题,再沿着另一维拓展延伸。

对于求矩形面积并的问题,从多维视角上看,矩形面积实际上是一维的线段沿着另一维移动扫过的面积。这个问题转换到一维就是求出每个时刻原图形的截线长度,下图黄线所示就是若干时刻的截线:
在这里插入图片描述
更进一步,我们可以发现截线的长度仅在经过长方形的边时发生改变,所以有用的截线实际上只有这几条:
在这里插入图片描述
而截线长度的变化就是因为添加/删去了矩形的边,所以我们把矩形拆成上边和下边,从下往上遍历每个边,遇到下边时对覆盖的区间加一,遇到上边则对覆盖的区间减一;每完成一次更新,就乘以相邻边的高度差,如此就能得到矩形并的面积。

要完成区间加和整体求和的操作,只需要在离散化横坐标的基础上建立线段树维护区间被覆盖次数 c o v cov cov和本区间内被覆盖的总长度 l e n len len

代码

由于横坐标划分的区间是在实数域上的,所以在边界处理上与区间为整数域的线段树有所不同:

#include<bits/stdc++.h>
#define ls v<<1
#define rs v<<1|1
using namespace std;
const int M=1e6+5;
struct Edge{
    int le,ri,h,val;
}edge[M<<1];
bool cmp(Edge a,Edge b){return a.h<b.h;}
struct node{
    int le,ri,len,cov;
}tree[M<<2];
int n,tot,x[M],cot;
long long ans;
void up(int v){tree[v].len=tree[v].cov?tree[v].ri-tree[v].le:tree[ls].len+tree[rs].len;}
void build(int v,int le,int ri)
{
    tree[v].le=x[le],tree[v].ri=x[ri+1];
    if(le==ri)return;
    int mid=le+ri>>1;
    build(ls,le,mid),build(rs,mid+1,ri);
}
void cover(int v,int le,int ri,int val)
{
    if(le<=tree[v].le&&tree[v].ri<=ri)
    {
        tree[v].cov+=val;up(v);
        return;
    }
    if(le<tree[ls].ri)cover(ls,le,ri,val);
    if(tree[rs].le<ri)cover(rs,le,ri,val);
    up(v);
}
void in()
{
    scanf("%d",&n);
    for(int i=1,a,b,c,d;i<=n;++i)
    scanf("%d%d%d%d",&a,&b,&c,&d),edge[++cot]=(Edge){a,c,b,1},edge[++cot]=(Edge){a,c,d,-1},x[++tot]=a,x[++tot]=c;
}
void ac()
{
    sort(x+1,x+1+tot);
    tot=unique(x+1,x+1+tot)-x-1;
    build(1,1,tot-1);
    sort(edge+1,edge+1+cot,cmp);
    for(int i=1;i<=cot;++i)
    {
        ans+=1ll*tree[1].len*(edge[i].h-edge[i-1].h);
        cover(1,edge[i].le,edge[i].ri,edge[i].val);
    }
    printf("%lld\n",ans);
}
int main()
{
    in(),ac();
    //system("pause");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值