[POJ1151][HDU1542]Atlantis(线段树,扫描线)
英文题面,我就只放个传送门了。
Solution
题意是算矩形面积并,这是扫描线算法能解决的经典问题。
算法的大致思想是,把每一个矩形拆成上边和下边(以下称作扫描线),每条扫描线有四个参数l,r,h,v。l和r为它的左右端点的横坐标,h为扫描线的纵坐标,v下面再解释。
然后把扫描线按h从小到大排序,想一想,所有相邻扫描线之间的有效面积(即被矩形覆盖的面积)加起来是不是就是ans?
怎么求呢?我们从下往上处理,设当前处理到第i条扫描线,设第i条扫描线与第i+1条扫描线之间的有效面积为s,那么s=(h[i+1]-h[i])*此时x轴被覆盖的长度。
考虑用线段树来维护这个“x轴被覆盖的长度“,处理到第i条扫描线时,就把区间[l[i],r[i]]覆盖一次。但是这个覆盖是有时限的,当扫到某一条上边时,它对应的下边所产生的覆盖就应该被消去。
为了方便地处理,我们把下边的v值设为1,上边的v值设为-1,这样修改时直接把区间[l[i],r[i]]的覆盖次数加v就好了。
具体实现时,x坐标要离散化,线段树中用cnt来表示区间被覆盖的次数,sum来表示区间(当然都是在x轴上)内的覆盖长度。
注意两点:
- 线段树上一个叶子节点i实际上表示的是x轴上[i,i+1]这一段,因此线段树只需要n-1个叶子节点。
- 由于我们只需要查询sum[1],所以update找到需修改的区间可以直接pushup,并且不用pushdown。
Code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=220; struct line{ double l,r,h;int v; line(){} line(double a,double b,double c,int d):l(a),r(b),h(c),v(d){} bool operator < (const line &tmp)const{return h<tmp.h;} }L[N]; int cnt[N<<2]; double X[N],sum[N<<2]; #define tl id<<1 #define tr id<<1|1 #define mid ((l+r)>>1) #define lson tl,l,mid #define rson tr,mid+1,r void pushup(int id,int l,int r){ if(cnt[id]) sum[id]=X[r+1]-X[l]; else if(l==r) sum[id]=0; else sum[id]=sum[tl]+sum[tr]; } void update(int id,int l,int r,int ll,int rr,int v){ if(ll<=l&&r<=rr){ cnt[id]+=v; pushup(id,l,r); return ; } if(ll>mid) update(rson,ll,rr,v); else if(rr<=mid) update(lson,ll,rr,v); else update(lson,ll,rr,v),update(rson,ll,rr,v); pushup(id,l,r); } int n,m,cas; int main(){ while(~scanf("%d",&n)&&n){ m=0;memset(cnt,0,sizeof cnt);memset(sum,0,sizeof sum); for(int i=0;i<n;++i){ double a,b,c,d; scanf("%lf%lf%lf%lf",&a,&b,&c,&d); L[m]=line(a,c,b,1); X[m++]=a; L[m]=line(a,c,d,-1); X[m++]=c; } sort(&L[0],&L[m]); sort(&X[0],&X[m]);n=unique(&X[0],&X[m])-X; double s=0; for(int i=0;i<m;++i){ int l=lower_bound(&X[0],&X[n],L[i].l)-X,r=lower_bound(&X[0],&X[n],L[i].r)-X-1; update(1,0,n-1,l,r,L[i].v); s+=sum[1]*(L[i+1].h-L[i].h); } printf("Test case #%d\nTotal explored area: %.2lf\n\n",++cas,s); //POJ上提交G++的同学请改为%.2f } return 0; }
愿你有一天能和重要的人重逢