The input file is terminated by a line containing a single 0. Don‘t process it.
Output a blank line after each test case.
顾名思义,扫描法就是用一根想象中的线扫过所有矩形,在写代码的过程中,这根线很重要。方向的话,可以左右扫,也可以上下扫。方法是一样的,这里我用的是由下向上的扫描法。
如上图所示,坐标系内有两个矩形。位置分别由左下角和右上角顶点的坐标来给出。上下扫描法是对x轴建立线段树,矩形与y平行的两条边是没有用的,在这里直接去掉。如下图。
现想象有一条线从最下面的边开始依次向上扫描。线段树用来维护当前覆盖在x轴上的线段的总长度,初始时总长度为0。用ret来保存矩形面积总和,初始时为0。
由下往上扫描,扫描到矩形的底边时将它插入线段树,扫描到矩形的顶边时将底边从线段树中删除。而在代码中实现的方法就是,每条边都有一个flag变量,底边为1,顶边为-1。
用cover数组(通过线段树维护)来表示某x轴坐标区间内是否有边覆盖,初始时全部为0。插入或删除操作直接让cover[] += flag。当cover[] > 0 时,该区间一定有边覆盖。
开始扫描到第一条线,将它压入线段树,此时覆盖在x轴上的线段的总长度L为10。计算一下它与下一条将被扫描到的边的距离S(即两条线段的纵坐标之差,该例子里此时为3)。
则 ret += L * S. (例子里增量为10*3=30)
结果如下图
橙色区域表示已经计算出的面积。
扫描到第二条边,将它压入线段树,计算出此时覆盖在x轴上的边的总长度。
例子里此时L=15。与下一条将被扫描到的边的距离S=2。 ret += 30。 如下图所示。
绿色区域为第二次面积的增量。
接下来扫描到了下方矩形的顶边,从线段树中删除该矩形的底边,并计算接下来面积的增量。如下图。
蓝色区域为面积的增量。
此时矩形覆盖的总面积已经计算完成。 可以看到,当共有n条底边和顶边时,只需要从下往上扫描n-1条边即可计算出总面积。
(借鉴大神博客)
***************************************************************************************************************************
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 #define INF 0x3f3f3f3 6 #define MAX 110 7 #define L(rt) rt<<1 8 #define R(rt) rt<<1|1 9 10 struct segment //保存矩形上下边界 11 { 12 double l,r,h; //左右横坐标,纵坐标 13 int f; //-1为下边界,1为上边界 14 }ss[2*MAX]; 15 struct node //线段树节点 16 { 17 int l,r; 18 int cnt; //该节点被覆盖的情况 19 double len; //该区间被覆盖的总长度 20 int mid() 21 { return (l+r)>>1; } 22 }tree[2*MAX*4]; 23 double pos[MAX<<2]; 24 int nums; 25 26 int cmp(segment a,segment b) 27 { 28 return a.h<b.h; 29 } 30 31 void build(int a, int b ,int rt) 32 { 33 tree[rt].l=a; tree[rt].r=b; tree[rt].cnt=0; tree[rt].len=0; 34 if(a==b) return ; 35 int mid=tree[rt].mid(); 36 build(a,mid,L(rt)); 37 build(mid+1,b,R(rt)); 38 } 39 40 int binary(double key ,int low, int high) 41 { 42 while(low<=high) 43 { 44 int mid=(low+high)>>1; 45 if(pos[mid] == key) return mid; 46 else if(key < pos[mid]) high=mid-1; 47 else low=mid+1; 48 } 49 return -1; 50 } 51 52 void get_len(int rt) 53 { 54 if(tree[rt].cnt) //非0,已经被整段覆盖 55 tree[rt].len = pos[tree[rt].r+1] - pos[tree[rt].l]; 56 else if(tree[rt].l == tree[rt].r) //已经不是一条线段 57 tree[rt].len = 0; 58 else //是一条线段但是又没有整段覆盖,那么只能从左右孩子的信息中获取 59 tree[rt].len = tree[L(rt)].len + tree[R(rt)].len ; 60 } 61 62 void updata(int a, int b ,int val ,int rt) 63 { 64 if(tree[rt].l==a && tree[rt].r==b) //目标区间 65 { 66 tree[rt].cnt += val; //更新这个区间被覆盖的情况 67 get_len(rt); //更新这个区间被覆盖的总长度 68 return ; 69 } 70 int mid=tree[rt].mid(); 71 if(b<=mid) //只访问左孩子 72 updata(a,b,val,L(rt)); 73 else if(a>mid) //只访问有孩子 74 updata(a,b,val,R(rt)); 75 else //左右都要访问 76 { 77 updata(a,mid,val,L(rt)); 78 updata(mid+1,b,val,R(rt)); 79 } 80 get_len(rt); //计算该区间被覆盖的总长度 81 } 82 83 int main() 84 { 85 int Case=0; 86 int n; 87 while(scanf("%d",&n)!=EOF && n) 88 { 89 nums=0; 90 for(int i=0; i<n; i++) 91 { 92 double x1,y1,x2,y2; 93 scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); 94 ss[nums].l=x1; ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=1; 95 //记录上边界的信息 96 ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=-1; 97 //记录下边界的信息 98 pos[nums]=x1; pos[nums+1]=x2; 99 //记录横坐标 100 nums += 2; 101 102 } 103 104 sort(ss,ss+nums,cmp); //横线按纵坐标升序排序 105 sort(pos,pos+nums); //横坐标升序排序 106 //for(int i=0; i<nums; i++) printf("%.2lf %.2lf %.2lf\n",ss[i].l,ss[i].r,ss[i].h); 107 int m=1; 108 for(int i=1; i<nums; i++) 109 if(pos[i]!=pos[i-1]) //去重 110 pos[m++]=pos[i]; 111 112 build(0,m-1,1); //离散化后的区间就是[0,m-1],以此建树 113 double ans=0; 114 for(int i=0; i<nums; i++) //拿出每条横线并且更新 115 { 116 int l=binary(ss[i].l,0,m-1); 117 int r=binary(ss[i].r,0,m-1)-1; 118 updata(l,r,ss[i].f,1); //用这条线段去更新 119 ans += (ss[i+1].h-ss[i].h)*tree[1].len; 120 //printf("%.2lf\n",ans); 121 } 122 printf("Test case #%d\n",++Case); 123 printf("Total explored area: %.2f\n\n",ans); 124 } 125 return 0; 126 }