扫描线是一种用来处理矩形相交的面积问题的算法
渐近时间复杂度约为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
Q1.
在坐标系给定n个矩形(以左下/右上角坐标给出)
求这些矩形面积的并
例如下图
n=2
矩形1: (1,1) (3,3)
矩形2: (2,2) (4,4)
A1.
扫描线的过程大致可以描述为
将整个面积并 以n个矩形的 2n条纵边为界 分割为2n-1个部分求解
即上图中我们作如下分割求解
我们要求的面积就是
a
n
s
=
S
1
+
S
2
+
S
3
ans=S1+S2+S3
ans=S1+S2+S3
再细致一点讲
我们记录每条纵边并按x坐标升序排序
依次遍历每条纵边边,同时记录当前纵边覆盖的y轴总长度len
遍历到的纵边为一个矩形的左边界时,len增加,反之为右边界时,len减小
根据当前遍历到的纵边
i
i
i更新完len后
令
a
n
s
+
=
(
x
i
+
1
−
x
i
)
∗
l
e
n
ans+=(x_{i+1}-x_i)*len
ans+=(xi+1−xi)∗len (
x
i
+
1
x_{i+1}
xi+1为下一条扫描线的横坐标,
x
i
x_i
xi为当前扫描线横坐标)
最关键的问题来了,len是如何更新的呢
设一个矩形表示为
(
x
1
,
y
1
)
(
x
2
,
y
2
)
(x_1,y_1)(x_2,y_2)
(x1,y1)(x2,y2)
我们记录这个矩形的扫描线为两个四元组
(
x
1
,
y
1
,
y
2
,
k
=
1
)
(
x
2
,
y
1
,
y
2
,
k
=
−
1
)
(x_1,y_1,y_2,k=1)(x_2,y_1,y_2,k=-1)
(x1,y1,y2,k=1)(x2,y1,y2,k=−1)其中左边界记录的扫描线k=1,右边界则为-1
最后我们共记录了2n条扫描线
定义
c
o
v
[
i
]
=
v
cov[i]=v
cov[i]=v表示
y
y
y轴上区间
[
i
,
i
+
1
]
[i,i+1]
[i,i+1]共被覆盖了
v
v
v次
将这些扫描线按x升序排序后
设当前遍历到的扫描线为
(
x
i
,
y
i
,
1
,
y
i
,
2
,
k
)
(x_i,y_{i,1},y_{i,2},k)
(xi,yi,1,yi,2,k)
我们令
c
o
v
[
y
i
,
1
]
cov[y_{i,1}]
cov[yi,1]~
c
o
v
[
y
i
,
2
−
1
]
cov[y_{i,2}-1]
cov[yi,2−1]都加k,即更新此时的覆盖情况
那么此时有
l
e
n
=
∑
c
o
v
[
i
]
>
0
1
len=\sum_{cov[i]>0}1
len=∑cov[i]>01,更新
a
n
s
+
=
(
x
i
+
1
−
x
i
)
∗
l
e
n
ans+=(x_{i+1}-x_i)*len
ans+=(xi+1−xi)∗len
对cov数组的更新显然可以用线段树优化
用len[p]表示结点p对应的区间内被覆盖的总长度
传统的延迟标记作用是查询带标记结点的子节点时下推更新
而注意到这里每次只查询len[1]的值(即根结点的值),所以不用延迟标记
对某个结点修改cov时直接自下向上更新,进行如下更新
void pushup(int p,int s,int t)
{
if(cov[p]>0) len[p]=t+1-s;
else if(s==t) len[p]=0;
else len[p]=len[p<<1]+len[p<<1|1];
}
void update(int ll,int rr,int s,int t,int p,int w)//调用时应传入区间[y1,y2-1]
{
if(ll<=s&&t<=rr){ cov[p]+=w; pushup(p,s,t); return;}
int mid=s+t>>1;
if(ll<=mid) update(ll,rr,s,mid,p<<1,w);
if(rr>mid) update(ll,rr,mid+1,t,p<<1|1,w);
pushup(p,s,t);
}
修改完后当前的总覆盖区间就是len[rt]
由于扫描线的题一般y都很大,且存在浮点数,所以要对y进行离散化
(
v
a
l
[
y
i
]
val[y_i]
val[yi]表示
y
i
y_i
yi离散化后的数值,
p
o
s
[
i
]
pos[i]
pos[i]表示
i
i
i对应的原数值)
离散化后对于四元组
(
x
i
,
y
i
,
1
,
y
i
,
2
,
k
)
(x_i,y_{i,1},y_{i,2},k)
(xi,yi,1,yi,2,k)
我们的更新区间为
[
v
a
l
[
y
i
,
1
]
,
v
a
l
[
y
i
,
2
]
−
1
]
[val[y_{i,1}],val[y_{i,2}]-1]
[val[yi,1],val[yi,2]−1]
而对应的上推变为
void pushup(int p,int s,int t)
{
if(cov[p]>0) len[p]=pos[t+1]-pos[s];
else if(s==t) len[p]=0;
else len[p]=len[p<<1]+len[p<<1|1];
}
扫描线应用
POJ - 1151 Atlantis【扫描线】
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef double dd;
int read()
{
int x=0,f=1;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return x*f;
}
const int maxn=2010;
int n,cs;
int tot,cnt;
struct Line{dd x,y1,y2;int k;}line[maxn];
bool cmp(Line a,Line b){return a.x<b.x;}
dd a[maxn],pos[maxn];
dd len[maxn<<2],ans;
int cov[maxn<<2];
void init()
{
cnt=tot=0; ans=0;
memset(len,0,sizeof(len));
memset(cov,0,sizeof(cov));
}
void pushup(int p,int s,int t)
{
if(cov[p]>0) len[p]=pos[t+1]-pos[s];
else if(s==t) len[p]=0;
else len[p]=len[p<<1]+len[p<<1|1];
}
void update(int ll,int rr,int s,int t,int p,int v)
{
if(ll<=s&&t<=rr){ cov[p]+=v; pushup(p,s,t); return;}
int mid=s+t>>1;
if(ll<=mid) update(ll,rr,s,mid,p<<1,v);
if(rr>mid) update(ll,rr,mid+1,t,p<<1|1,v);
pushup(p,s,t);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0) break; init();
for(int i=1;i<=n;i++)
{
dd x1,y1,x2,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[++tot].x=x1; line[tot].k=1;
line[tot].y1=y1; line[tot].y2=y2;
a[tot]=y1;
line[++tot].x=x2; line[tot].k=-1;
line[tot].y1=y1; line[tot].y2=y2;
a[tot]=y2;
}
sort(a+1,a+1+tot);
for(int i=1;i<=tot;++i)
if(i==1||a[i]!=a[i-1])
pos[++cnt]=a[i];
sort(line+1,line+1+tot,cmp);
for(int i=1;i<tot;i++)
{
int ll=lower_bound(pos+1,pos+1+cnt,line[i].y1)-pos;
int rr=lower_bound(pos+1,pos+1+cnt,line[i].y2)-pos;
update(ll,rr-1,1,cnt,1,line[i].k);
ans+=len[1]*(line[i+1].x-line[i].x);
}
printf("Test case #%d\n",++cs);
printf("Total explored area: %.2f\n\n",ans);
}
return 0;
}
HDU - 1255 覆盖的面积【扫描线】
求多个矩形的面积交,考察对扫描线的理解
l
e
n
[
p
]
[
0
]
len[p][0]
len[p][0]记录 结点p对应区间内至少被覆盖一次的长度
l
e
n
[
p
]
[
1
]
len[p][1]
len[p][1]记录 结点p对应区间内至少被覆盖两次的长度
求面积并的上推代码为
void pushup(int p,int s,int t)
{
if(cov[p]>0) len[p]=pos[t+1]-pos[s];
else if(s==t) len[p]=0;
else len[p]=len[p<<1]+len[p<<1|1];
}
我们在此基础上扩展
加入
c
o
v
[
p
]
≥
2
cov[p]\geq 2
cov[p]≥2的情况
并在原来三种情况的基础上加入
l
e
n
[
]
[
1
]
len[][1]
len[][1]的更新
void pushup(int p,int s,int t)
{
if(cov[p]>=2) len[p][0]=len[p][1]=pos[t+1]-pos[s];//加入cov[p]>=2的情况
else if(cov[p]==1)
{
len[p][0]=pos[t+1]-pos[s];
if(s==t) len[p][1]=0;//加入len[1][]的更新
else len[p][1]=len[p<<1][0]+len[p<<1|1][0];
}
else if(s==t) len[p][0]=len[p][1]=0;//原来只有len[0][p]=0;
else{
len[p][0]=len[p<<1][0]+len[p<<1|1][0];//cov[p]==0也加入len[1][]的更新
len[p][1]=len[p<<1][1]+len[p<<1|1][1];
}
}
完整代码
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef double dd;
int read()
{
int x=0,f=1;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return x*f;
}
const int maxn=2010;
int T,n;
int tot,cnt;
struct Line{dd x,y1,y2;int k;}line[maxn];
bool cmp(Line a,Line b){return a.x<b.x;}
dd a[maxn],pos[maxn];
dd len[maxn<<2][2],ans;
int cov[maxn<<2];
void init()
{
cnt=tot=0; ans=0;
memset(len,0,sizeof(len));
memset(cov,0,sizeof(cov));
}
void pushup(int p,int s,int t)
{
if(cov[p]>=2) len[p][0]=len[p][1]=pos[t+1]-pos[s];
else if(cov[p]==1)
{
len[p][0]=pos[t+1]-pos[s];
if(s==t) len[p][1]=0;
else len[p][1]=len[p<<1][0]+len[p<<1|1][0];
}
else if(s==t) len[p][0]=len[p][1]=0;
else{
len[p][0]=len[p<<1][0]+len[p<<1|1][0];
len[p][1]=len[p<<1][1]+len[p<<1|1][1];
}
}
void update(int ll,int rr,int s,int t,int p,int v)
{
if(ll<=s&&t<=rr){ cov[p]+=v; pushup(p,s,t); return;}
int mid=s+t>>1;
if(ll<=mid) update(ll,rr,s,mid,p<<1,v);
if(rr>mid) update(ll,rr,mid+1,t,p<<1|1,v);
pushup(p,s,t);
}
int main()
{
T=read();
while(T--)
{
n=read(); init();
for(int i=1;i<=n;i++)
{
dd x1,y1,x2,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[++tot].x=x1; line[tot].k=1;
line[tot].y1=y1; line[tot].y2=y2;
a[tot]=y1;
line[++tot].x=x2; line[tot].k=-1;
line[tot].y1=y1; line[tot].y2=y2;
a[tot]=y2;
}
sort(a+1,a+1+tot);
for(int i=1;i<=tot;++i)
if(i==1||a[i]!=a[i-1])
pos[++cnt]=a[i];
sort(line+1,line+1+tot,cmp);
for(int i=1;i<tot;i++)
{
int ll=lower_bound(pos+1,pos+1+cnt,line[i].y1)-pos;
int rr=lower_bound(pos+1,pos+1+cnt,line[i].y2)-pos;
update(ll,rr-1,1,cnt,1,line[i].k);
ans+=len[1][1]*(line[i+1].x-line[i].x);
}
printf("%.2lf\n",ans);
}
return 0;
}