题目大意
平面上有一些“回型”图案,每一个“回型”是由一个大矩形中间挖去一个小矩形构成,大小矩形的四边都平行于坐标轴。
现在有n个不同大小的“回型”图案,他们可能互相重叠,请求出被他们所覆盖的平面的总面积。
思路
首先,将每个一个回型划分为四个矩形。将每个矩形的与x轴平行的边按离x轴距离由近及远顺序插入线段树中(底边插入,顶边删除)。
在对每条线段执行插入之前,先计算下当前线段与上一条线段之间的覆盖面积。用tree[1].len*当前线段与前一个线段之间的距离(高度差),tree[1].len表示图形的宽。
当遍历完所有线段时,就会得出覆盖面积。
代码
#include<iostream> #include<stdio.h> using namespace std; #define MAXSIZE 50000 #define N 50000 struct node { int total; //total表示该节点所表示的区间被线段覆盖的次数 int left; int right; int len; //该区间内,线段的长度。 }tree[MAXSIZE*3]; struct line { int pos; //pos表示y轴坐标 int left; int right; bool top; //判断该线段是否是矩形的上边界。 }edge[N*8]; int total_edge,n; //一共total_edge个线段,n个回型。 void create(int root,int left,int right) //建树,明确每个节点对应的区间 { tree[root].left=left; tree[root].right=right; if(left==right) return ; int middle=(left+right)/2; create(2*root,left,middle); create(2*root+1,middle+1,right); } void updata(int root,int left,int right,int val) //将线段插入或删除。 { if(tree[root].left>right||tree[root].right<left) return ; else if(left<=tree[root].left&&tree[root].right<=right) tree[root].total+=val; else { updata(2*root,left,right,val); updata(2*root+1,left,right,val); } if(tree[root].total>0) tree[root].len=tree[root].right-tree[root].left+1; else if(tree[root].right==tree[root].left) tree[root].len=0; else tree[root].len=tree[2*root].len+tree[2*root+1].len; } void add_edge(int pos,int left,int right,bool top) //记录一条线段的信息 { if(left>right) return; edge[total_edge].pos=pos; edge[total_edge].left=left; edge[total_edge].right=right; edge[total_edge].top=top; total_edge++; } int cmp(const void* m,const void* n) //将线段按照y轴坐标由小到大排序。 { return (*(line*)m).pos-(*(line*)n).pos; } void init() //初始化及输入 { total_edge=0; for(int i=1;i<MAXSIZE*3;i++) { tree[i].len=0; tree[i].total=0; } for(int i=0;i<n;i++) { int x1,y1,x2,y2,x3,y3,x4,y4; scanf("%d%d%d%d%d%d%d%d",&x1,&y1,&x2,&y2,&x3,&y3,&x4,&y4); add_edge(y1+1,x1+1,x3,false); add_edge(y2+1,x1+1,x3,true); add_edge(y1+1,x3+1,x4,false); add_edge(y3+1,x3+1,x4,true); add_edge(y4+1,x3+1,x4,false); add_edge(y2+1,x3+1,x4,true); add_edge(y1+1,x4+1,x2,false); add_edge(y2+1,x4+1,x2,true); } } int main() { create(1,1,MAXSIZE+1); while(scanf("%d",&n)&&n) { init(); qsort(edge,total_edge,sizeof(edge[0]),cmp); long long total_size=0; for(int i=0,pre=1;i<total_edge;i++) { total_size+=(long long)tree[1].len*(edge[i].pos-pre); //这里注意,tree[1].len必须转化为long long型 updata(1,edge[i].left,edge[i].right,edge[i].top?-1:1); pre=edge[i].pos; } printf("%I64d\n",total_size); } }
后记
此题前前后后做了3天。主要原因是对线段树成段更新的不熟练(对延时标记处理得不够老练)。做此题之前只会成段赋值,不会成段增减。因此特地找到POJ 3468写了个成段增减的模板,为做此题打基础。(虽然后来发现此题与成段增减的关系貌似不大)
另外在调试此题的过程中,还发现了原先成段赋值模板(根据HDU 1698写的模板)的一处设计缺陷。在此模板中,将树中节点的int型延迟标记初始化为0,并根据延迟标记是否不为0来判断节点是否被更改。但这就产生了新的问题,若我想要将一个区间赋值为0该怎么办?此时延迟标记被赋值为0,节点会被误判为未更改。
历经千辛万苦,最终发现此题并不是什么成段增减。最终思路还是通过看作者题解得来的。