亚特兰蒂斯
算法涉及:线段树+扫描线+二分查找+离散化
先扯本题的大致思路:
给定一个n
代表地图数量,每张地图的有x1,y1,x2,y2
的信息,表示地图的左上角和右下角位置,我们要计算所有地图的面积和,重复覆盖部分仅计算一次。
再扯本题的做题思路:
1:扫描线的过程,假设我们有一根线自下向上扫描
「来源博客」
我们的线段树就是为了维护矩形的长,我们给每一个矩形的上下边进行标记,下面的边标记为1,上面的边标记为-1,每遇到一个矩形时,我们知道了标记为1的边,我们就加进来这一条矩形的长,等到扫描到-1时,证明这一条边需要删除,就删去,利用1和-1可以轻松的到这种状态。
那么可知,两条与
x
x
x平行的线之间的距离是易得的,那么我们需要用线段树维护扫描线上有效区间的长度,进而进行面积的更新,如:
a
n
s
+
=
t
r
[
i
]
.
l
e
n
∗
(
s
e
g
[
i
]
.
y
−
s
e
g
[
i
−
1
]
.
y
)
ans+=tr[i].len*(seg[i].y-seg[i-1].y)
ans+=tr[i].len∗(seg[i].y−seg[i−1].y)。
2:线段树的建立与维护
此题有个结论,矩形扫描,
l
a
z
y
lazy
lazy可以下放。(知道怎么使用即可),平行四边形等其他不规则图形不可下放
l
a
z
y
lazy
lazy。那么接下来就是构建线段树,此题我们需要维护的是区间,一共有
n
n
n个点,下标从
0
0
0开始,所以我们的
t
r
[
1
]
=
0
,
x
s
.
s
i
z
e
(
)
−
2
tr[1]={0,xs.size()-2}
tr[1]=0,xs.size()−2。每个叶子维护的是一段区间,所以我们最终每次要用到的
t
r
[
1
]
.
l
e
n
tr[1].len
tr[1].len是根据子区间
p
u
s
h
u
p
pushup
pushup上来的。
扫描线自左向右扫描参考代码:
#include<bits/stdc++.h>
using namespace std;
int n;
vector<double>ys;
const int N = 1e5+10;
struct Segment{
double x,y1,y2;//下标
int k;//矩形左边还是右边
bool operator<(const Segment &W)const{
return x<W.x;
}
}seg[N*2];//存点(左边和右边一共是2N)
struct Trie{
int l,r;
int cnt;//出现次数
double len;//区间长度
}tr[N*8];//2N*(4N线段树)
void pushup(int u){//分类讨论
if(tr[u].cnt){
tr[u].len=ys[tr[u].r+1]-ys[tr[u].l];
}
else if(tr[u].l!=tr[u].r){
tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
}
else tr[u].len=0;//叶子且没有cnt必然为0。
}
void build(int u,int l,int r){//刚开始的树的cnt和len都为0所以不用pushup。
tr[u]={l,r,0,0};
if(l!=r){
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
}
int find(double x){
return lower_bound(ys.begin(),ys.end(),x)-ys.begin();
}
void modify(int u,int l,int r,int k){
if(tr[u].l>=l&&tr[u].r<=r){
tr[u].cnt+=k;
pushup(u);
}
else{
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid)modify(u<<1,l,r,k);
if(r>mid)modify(u<<1|1,l,r,k);
pushup(u);
}
}
int main(){
int T=1;
while(cin>>n,n){
ys.clear();
for(int i=0,j=0;i<n;i++){
double x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
seg[j++]={x1,y1,y2,1};
seg[j++]={x2,y1,y2,-1};
ys.push_back(y1);
ys.push_back(y2);
}
sort(ys.begin(),ys.end());
ys.erase(unique(ys.begin(),ys.end()),ys.end());//去重离散化
build(1,0,ys.size()-2);//一共ys.size()-1段,下标从0开始
sort(seg,seg+2*n);
double res=0;//最终面积
for(int i=0;i<n*2;i++){
if(i>0)res+=tr[1].len*(seg[i].x-seg[i-1].x);//矩形求面积公式,tr[i].len表示覆盖区间长度
//例子:假设进行modify(1,find(10),find(15) - 1,1);
// 假设find(10) = 0,find(15) = 1;
// 此时为modify(1, 0, 0, 1);
// 表示线段树中0号点出现次数加1;
// 而线段树中0号点刚好为线段(10 ~ 15);
// 这就是为什么要进行find(seg[i].y2) - 1 的这个-1操作,因为我们需要维护的是区间。
modify(1,find(seg[i].y1),find(seg[i].y2)-1,seg[i].k);
}
printf("Test case #%d\n",T++);
printf("Total explored area: %.2lf\n\n",res);
}
return 0;
}