P5490 【模板】扫描线
求 \(n\) 个矩形的面积并。
我们可以用一根竖直的或者水平的线扫过坐标系,这样的话我们可以发现,面积的变化只会出现在两端线段的位置上。(这里我倾向于竖直的)
(图片是 @Gu_Pigeon 的)
也就是说,我们只需要记录一个矩形的左右两条竖直的边,也就是把一个矩形分成 \(2 \times n\) 段,每一段在扫描线上覆盖的长度乘上这一段的宽度就是这一段的矩形面积,把所有的矩形面积都加起来就可以获得面积并了。
。在本题中,题目会给出左下角和右上角的坐标,于是我们可以记录左右两条边。分别用一个四元组来记录:
\((x_1,y_1,y_2,1)\)
\((x_2,y_1,y_2,-1)\)
struct Line{
ll x,low,high;
int mark;
bool operator < (const Line &a)const{
return x < a.x;
}
}line[maxn<<1];
其中我们假设 \(x_1<x_2,y_1<y_2\) ,并且用 \(1/-1\)
来表示这个边是左边还是右边。
之后我们可以对这些四元组按照 \(x\) 递增排序。
之后要对这些线段进行离散化。
ll y[maxn<<1];
int main(){
n=read();
n<<=1;
for(re int i=1;i<=n;i+=2){
xx=read();yy=read();x2=read();y2=read();
y[i]=yy,y[i+1]=y2;
}
sort(y+1,y+n+1);
int tot=unique(y+1,y+n+1)-y-1;
}
(@CYJian提供了一种不需要离散化的做法,可以用标记永久化和动态开点偷个懒)
离散化以后,会出现 \(tot\) 个不同的纵坐标值,于是我们的扫描线最多会被分成 \(tot-1\) 段。
我们开一个数组 \(cnt[]\) 来记录每一段被覆盖的次数。
扫描的过程中,如果当前四元组是 \((x_1,y_1,y_2,k)\) ,我们就把 \(cnt[y[y_1]],cnt[[y_1]+1],\cdots,cnt[[y_2]-1]\) 都加上 \(k\) 。
在扫到下一个四元组的过程中,被覆盖的长度就等于 \(\sum \limits_{c[i]>0} (y[i+1]-y[i])\)
对于 \(cnt\) 数组,我们可以使用线段树来维护,这样就可以 \(O(n^2)->O(nlogn)\)
线段树需要维护两个内容:
1.这个线段被覆盖了多少次
2.这个线段被整个矩形覆盖的长度
struct SegmentTree{
int v;
ll len;
}st[maxn<<2];
对于一个四元组,我们在 \([y_1,y_2-1]\)
上执行区间修改操作。
对于线段树中任意一个节点 \([l,r]\) ,如果 \(sum>0\) 那么 \(len=y[r+1]-y[l]\),
否则, \(len\) 等于两个子节点的 \(len\) 之和。最后根节点的 \(len\) 就是整个扫描线上被覆盖的区间长度。
//#define LawrenceSivan
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
typedef long long ll;
#define re register
#define ls rt<<1
#define rs rt<<1|1
int n,cnt;
ll xx,yy,x2,y2,y[maxn<<1];
struct Line{
ll x,low,high;
int mark;
bool operator < (const Line &a)const{
return x<a.x;
}
}line[maxn<<1];
struct SegmentTree{
int v;
ll len;
}st[maxn<<2];
void push_up(int rt,int l,int r){
if(st[rt].v)st[rt].len=y[r+1]-y[l];
else st[rt].len=st[ls].len+st[rs].len;
}
void modify(int rt,int l,int r,ll ql,ll qr,int c){
if(ql>=y[r+1]||qr<=y[l])return;
if(ql<=y[l]&&y[r+1]<=qr){
st[rt].v+=c;
push_up(rt,l,r);
return;
}
int mid=(l+r)>>1;
modify(ls,l,mid,ql,qr,c);
modify(rs,mid+1,r,ql,qr,c);
push_up(rt,l,r);
}
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
int main(){
#ifdef LawrenceSivan
freopen("horizon.in","r",stdin);
freopen("horizon.out","w",stdout);
#endif
n=read();
n<<=1;
for(re int i=1;i<=n;i+=2){
xx=read();yy=read();x2=read();y2=read();
y[i]=yy,y[i+1]=y2;
line[i]=(Line){xx,yy,y2,1};
line[i+1]=(Line){x2,yy,y2,-1};
}
sort(line+1,line+n+1);
sort(y+1,y+n+1);
int tot=unique(y+1,y+n+1)-y-1;
ll ans=0;
for(re int i=1;i<n;i++){
modify(1,1,tot-1,line[i].low,line[i].high,line[i].mark);
ans+=st[1].len*(line[i+1].x-line[i].x);
}
printf("%lld\n",ans);
return 0;
}