经典扫描线题,算法网上有很详细的,这里简单说一下:
把每个矩形拆成两条竖边,标记每条边是左边的还是右边的,把所有的边按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; }