题解:P5490 【模板】扫描线 & 矩形面积并
前置芝士
请务必学透再来做本题。
线段树
离散化
扫描线算法介绍
扫描线算法,专门用于解决多个矩形的面积并问题,算法实现流程跟名字一样。
这是我从 OIwiki 搬运的算法实现过程视频。
通过观察视频,不难发现,计算共分成了 5 个部分,而每个部分的面积,就是一个矩形的面积,直接计算即可(不会的请回到小学再学一次)。
矩形的高可轻易求得,在此不赘述。
现在,重点就是求矩形的底了。再回顾一次视频中扫描方式,可以发现每次扫描到一根线段,我们就会把它加入或者删除。因为每次坐标均为整数,所以可以直接区间加上或者减去 1,最后区间求和即可。
区间加,大家想到什么?线段树对吧。为了加速,考虑使用线段树维护。
在线段树上进行修改,请自行学习,至于树上查询,因为我们需要全部的数量之和,直接查询根节点即可。
整体时间复杂度:
别忘了离散化!!!
别忘了离散化!!!
:::
正确性证明
对于每行:因为每一次当前行中算的是覆盖的部分,重叠的部分只会计算一次,且不重不漏。(因为每次查找的是大于等于 1 的数字个数)。
对于不同行:由于行不同,自然不会产生重叠。
时间复杂度证明
离散化:我采用 sort、unique 和 lower_bound 进行离散化,时间复杂度
区间修改操作:线段树操作,时间复杂度
查询操作:查询总和即可,时间复杂度
总和:省略常数项为
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,a[3999999],tr[3999999],bj[3999999],lbj[3999999],tot,buc[1008617],len[1008617];
struct nd
{
int l,r,y,k;
}tree[1008617];
void build(int l,int r,int now)
{
len[now]=buc[r]-buc[l-1];
if(l==r)return;
int mid=l+((r-l)/2);
build(l,mid,now*2);
build(mid+1,r,now*2+1);
}
void update(int u)
{
if(lbj[u]>0)tr[u]=len[u];
else tr[u]=tr[2*u]+tr[2*u+1];
}
void xg(int l,int r,int to,int l1,int r1,int now)
{
if(l<=l1&&r1<=r)
{
lbj[now]+=to;
update(now);
return;
}
int mid=l1+(r1-l1)/2;
if(l<=mid)xg(l,r,to,l1,mid,now*2);
if(r>mid)xg(l,r,to,mid+1,r1,now*2+1);
update(now);
}
bool cmp(nd aa,nd bb)
{
return aa.y<bb.y;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int xl,xr,yl,yr;
cin>>xl>>yl>>xr>>yr;
tree[i].l=xl;
tree[i].r=xr;
tree[i].y=yl;
tree[i].k=1ll;
tree[i+n].l=xl;
tree[i+n].r=xr;
tree[i+n].y=yr;
tree[i+n].k=-1ll;
buc[++tot]=xl;
buc[++tot]=xr;
}
sort(buc+1,buc+tot+1);
tot=unique(buc+1,buc+1+tot)-buc-1;
for(int i=1;i<=2*n;i++)
{
tree[i].l=lower_bound(buc+1,buc+1+tot,tree[i].l)-buc;
tree[i].r=lower_bound(buc+1,buc+1+tot,tree[i].r)-buc;
}
sort(tree+1,tree+1+2*n,cmp);
build(1,tot,1);
int ans=0;
for(int i=1;i<=2*n;i++)
{
ans+=(tree[i].y-tree[i-1].y)*tr[1];
xg(tree[i].l+1,tree[i].r,tree[i].k,1,tot,1);
}
cout<<ans;
return 0;
}
小练习
别看我,我除了前两题全都不会做……
【模板】离线二维数点
矩形周长
扇形面积并
纵坐标扫描线
洪水