Input
Output
Sample Input
2 5 1 1 4 2 1 3 3 7 2 1.5 5 4.5 3.5 1.25 7.5 4 6 3 10 7 3 0 0 1 1 1 0 2 1 2 0 3 1
Sample Output
7.63 0.00 一道线段树的经典题目。 要解决这个问题,必须要知道如何用线段树求矩形面积并,可以在我的这篇博客中看一下, 当然也可以自己搜索啦。http://blog.csdn.net/wr132/article/details/49797279 那么,当我们已经知道了如何求矩形面积并,问题已经解决了一半,因为求矩形面积并的 问题可以抽象为 求被覆盖了一次及一次以上的区域的面积;而我们的问题是 求被覆盖了两次及两次以上的区域的面积 这样大家就可一看出两道题目的共通之处了,接下来要解决浮点数的问题,我们在计算 矩形面积的时候,需要计算出x轴区域内被覆盖了两次及两次以上的区域的长度,并且这个 长度随着边的加入和删除是要实时更新的,而更新需要使用到线段树,关键是线段树的区间 得是整数,那么我们就需要对x轴做离散化,将浮点的坐标点映射为连续整数坐标点,这里 要注意一下坐标点的含义,它并不表示一个单纯的点,而是一个从该店开始,到下一个点 的一段区间,因为这个连续的区间是不可再分的,因此我们称其元区间 ,这些区间处于线段树 中的叶子的位置。 下面我再来看线段树。首先,其中的变量有l,r即当前节点的左右区间端点,以及 cnt,这是一个很重要的变量,因为我们是以所有矩形的边为基础进行离散化的,因此在输入 边的时候,就会出现这条边的一部分恰好完全覆盖这个区间的情况,这时候cnt就会相应 的增加或者减少,也就是说,cnt含义是恰好覆盖这个区间的边的数量。还有一个数组 len[2],len[0]表示该区间中覆盖次数大于0次的区域的长度,len[1]表示该区域中覆盖 次数大于1次的区域的长度,这是我们计算的关键,因为我们最终要获得的就是根节点的 len[1],即整个区间中覆盖次数大于1次的区域的长度。那么len到底怎么计算呢? 对于len[0] 1.if当前节点的cnt大于0,那么这个区间内必定包含至少一条完全覆盖区间的边, 因此tr[rt].len[0]=pos[tr[rt].r+1]-pos[tr[rt].l],这里注意,为什么 要把右区间加1,还记得我们说叶子节点也表示一个区间,是从这个点开始到下一个点,也就是 说区间端点是左闭右开的,最右边的点是不包含的,那是因为区间数要比点数少1。 2.else if当前节点是叶子节点,那么这个节点的len[0]肯定是0,因为没有比它 更短的区间了。 3.else,不知道当前节点的覆盖状态,只能由其两个子区间的len[0]值的和来决定。 对于len[1] 1.if当前节点的cnt大于1, 那么这个区间内必定包含至少两条完全覆盖区间的边, 因此len[1]可以直接就等于这个区间的长度 2.else if当前节点是叶子节点,那么其len[1]必定为0,因为没有比它更小的区间 可以帮它覆盖两次以上。 3.else if当前节点的cnt==1,那么我们可以确定一定有一条边完全覆盖了这个区间, 那么只要存在一条任意长度的边出现在这个区间内,就可以和那条长边构成覆盖两次及以上, 因此我们只需要去看看左右孩子的len[0],把他们相加就是该节点的len[1]。 4.else 当前节点的cnt==0,也就是说这个大区域的覆盖情况未知,那没办法,只能缩小 面积,查看子区间的len[1],将其相加即可得到这个大区间的len[1]。 当以上的工作都准备完成后,我们就可以计算面积了,方法很简单,每一次向线段树中插入一条边, 更新线段树,并且由根节点的len[1]值和相邻两条线段的高度差计算出一个小矩形的面积, 将面积累加,不要忘了最后保留两位小数。#include <iostream> #include <stdio.h> #include <algorithm> #define maxn 1010 #define lson(rt) rt<<1 #define rson(rt) rt<<1|1 using namespace std; struct segment//保存没条线段的信息 { double l,r,h; int v; }; struct node//保存线段树中的节点 { int l,r; int cnt;//表示与这个区间完全吻合的线段的个数 double len[2]; }; double pos[maxn<<1]; segment s[maxn<<1]; node tr[maxn<<3]; bool cmp(const segment &a,const segment &b) { return a.h<b.h; } void push_up(int rt) { //更新覆盖1次及1次以上的区域的长度 if(tr[rt].cnt) tr[rt].len[0]=pos[tr[rt].r+1]-pos[tr[rt].l]; else if(tr[rt].l==tr[rt].r) tr[rt].len[0]=0; else tr[rt].len[0]=tr[lson(rt)].len[0]+tr[rson(rt)].len[0]; //更新覆盖2次及2次以上区域的长度 if(tr[rt].cnt>1) tr[rt].len[1]=pos[tr[rt].r+1]-pos[tr[rt].l]; else if(tr[rt].l==tr[rt].r) tr[rt].len[1]=0; else if(tr[rt].cnt==1) tr[rt].len[1]=tr[lson(rt)].len[0]+tr[rson(rt)].len[0]; else tr[rt].len[1]=tr[lson(rt)].len[1]+tr[rson(rt)].len[1]; } void build(int l,int r,int rt) { tr[rt].l=l;tr[rt].r=r; tr[rt].cnt=tr[rt].len[0]=tr[rt].len[1]=0; if(l==r) return; int m=(tr[rt].l+tr[rt].r)>>1; build(l,m,lson(rt)); build(m+1,r,rson(rt)); } int bin_search(double val,int l,int r)//二分查找 { int m; while(l<=r) { m=(l+r)>>1; if(pos[m]==val) { //printf("m=%d\n",m); return m; } if(val<=pos[m]) r=m; else l=m+1; } return -1; } void update(int l,int r,int v,int rt) { if(tr[rt].l==l&&tr[rt].r==r) { tr[rt].cnt+=v; push_up(rt); return; } int m=(tr[rt].l+tr[rt].r)>>1; if(r<=m) update(l,r,v,lson(rt)); else if(l>m) update(l,r,v,rson(rt)); else { update(l,m,v,lson(rt)); update(m+1,r,v,rson(rt)); } push_up(rt); } int main() { int t,n,p,i,l,r; double x1,y1,x2,y2; double res; scanf("%d",&t); while(t--) { res=0; p=0; scanf("%d",&n); while(n--) { scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); pos[p]=x1; pos[p+1]=x2; s[p].l=s[p+1].l=x1; s[p].r=s[p+1].r=x2; s[p].h=y1; s[p+1].h=y2; s[p].v=1; s[p+1].v=-1; p+=2; } sort(pos,pos+p); sort(s,s+p,cmp); int m=1; for(i=1;i<p;i++)//去除相同的x值 if(pos[i]!=pos[i-1]) pos[m++]=pos[i]; build(0,m-1,1); for(i=0;i<p-1;i++)//最后一条边一定是顶边,不需要插入 { l=bin_search(s[i].l,0,m-1); r=bin_search(s[i].r,0,m-1)-1;//注意这里的区间端点是前闭后开 //printf("l=%d,r=%d\n",l,r); update(l,r,s[i].v,1); res+=tr[1].len[1]*(s[i+1].h-s[i].h); } res+=0.004; printf("%.2f\n",res); } return 0; }