经典扫描线题,算法网上有很详细的,这里简单说一下:
把每个矩形拆成两条竖边,标记每条边是左边的还是右边的,把所有的边按x坐标排序。用一个数组cov标记整个y轴每个部分在多少个矩形中,哪些不是。把浮点数离散化。
然后开始扫描:先把答案加上(全部在矩形中的区间的长度和)×(这条边x坐标和前一条边x坐标之差),代表这两条边之间被覆盖的面积,然后这条边如果是左边,就把所覆盖区间的cov数组+1,是右边就-1.
区间加和查询用线段树实现,但是是一个特殊的线段树。
1.对于区间(l,r),它的子区间是(l,mid)和(mid,r)。
2.不需要lazy数组,因为查询的区间永远只有一个就是最大区间,所以更新某个区间后不需要更新它的子区间。
3.pushup时,如果cov[rt]大于0,长度就是整个区间,否则就是两个子区间长度和(不是0)
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define lson l,mid,rt<<1
#define rson mid,r,rt<<1|1
using namespace std;
#include <set>
#include <map>
set<double> Rem;
map<double,int> Hash;
double a[1000];
int cov[1000];
int N;
struct line{
double x,iy1,iy2;
int y1,y2;
int c;
}Line[205];
double Y[205];
bool cmp(line a,line b){
if(a.x==b.x) return a.c<b.c;
return a.x<b.x;
}
void pushup(int l,int r,int rt){
if(cov[rt]>0) a[rt]=Y[r]-Y[l];
else a[rt]=a[rt<<1]+a[rt<<1|1];
}
void update(int l,int r,int rt,int L,int R,int c){
if(l>=L&&r<=R){
cov[rt]+=c;
pushup(l,r,rt);
return ;
}
int mid=(l+r)/2;
if(mid>L) update(lson,L,R,c);
if(mid<R) update(rson,L,R,c);
pushup(l,r,rt);
}
int main(){
int kase=1;
while(~scanf("%d",&N)){
if(!N) break;
memset(a,0,sizeof(a));
memset(cov,0,sizeof(cov));
memset(Y,0,sizeof(Y));
Rem.clear();
Hash.clear();
for(int i=0;i<N;i++){
double x1,x2,y1,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
Line[i].x=x1;Line[i].c=1;
Line[i].iy1=y1;Line[i].iy2=y2;
Line[i+N].x=x2;Line[i+N].c=-1;
Line[i+N].iy1=y1;Line[i+N].iy2=y2;
Rem.insert(y1);Rem.insert(y2);
}
set<double>::iterator p;
int cnt=0;
for(p=Rem.begin();p!=Rem.end();p++){
Hash[*p]=++cnt;
Y[cnt]=*p;
}
sort(Line,Line+2*N,cmp);
for(int i=0;i<N*2;i++){
Line[i].y1=Hash[Line[i].iy1];
Line[i].y2=Hash[Line[i].iy2];
}
update(1,cnt,1,Line[0].y1,Line[0].y2,Line[0].c);
double res=0;
for(int i=1;i<2*N;i++){
res+=(Line[i].x-Line[i-1].x)*a[1];
update(1,cnt,1,Line[i].y1,Line[i].y2,Line[i].c);
}
printf("Test case #%d\n",kase++);
printf("Total explored area: %.2f\n\n",res);
}
return 0;
}