给你n个矩形,每个矩形给出左下点的坐标,右上点的坐标。最后以n=0为结束。要你求出矩形并后的面积。
假设从上到下四条线段的Y值为y1,y2,y3,y4,他们的宽度各位d1,d2,d3,d4。那么首先从上到下扫描第一条线段,此时投影到红色线条的长度为d1,于是面积是(y2-y1)*d1(右图紫色区域),之后扫描第二条线,投影到红色线条的长度并不仅仅是d1+d2,两者存在重叠,这时利用线段树,求助所有线段的投影长度再乘以(y3-y2)即是中间蓝色区域的面积。
扫描第三条线的时候,其实是矩形的底边,这时应该从线段树中除去第一条边,因为扫描到底边,说明该矩形已经扫描完成,不再对红色线条的投影长度有任何贡献。因此上图绿色区域的面积是(d1+d2-d3)*(y4-y3),d1和d3相等。
总结,投影的区域全部存放在线段树内
离散化处理的目的是减少存储空间,假如序列是1 4 5 9,那么需要开出[1,9]的空间,离散化后:[1 2 3 4]只要四个,再用另一个数组保存对应的原始长度。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 300
struct Node
{
double x;double y1;double y2;
int flag;
}node[N];
bool cmp(Node a,Node b)
{
return a.x-b.x<0.0000001;
}
double y[N];
struct node
{
int l;int r;double ml;double mr;int s;double len;
}a[N*3];
// 空间<2^0+2^1+...+2^(1+log(L-1))=4(L-1)-1 )
void build(int i,int left,int right)
{
a[i].l=left;//a是线段树。left,right是离散的Y值,范围是【1,t-1】,因为线段树必须是连续的,根节点为[1-t-1] 离散时与NODE无关,node是真实线段
a[i].r=right;
a[i].ml=y[left];//离散之前对应的原始Y值,1对应最小的Y值,t-1对应最大的Y值
a[i].mr=y[right];
a[i].s=0;
a[i].len=0;
if(a[i].l+1==a[i].r)
{
return ;
}
int mid=(left+right)>>1;
build(i*2,left,mid);
build(i*2+1,mid,right);//建树时注意这里不是mid+1,因为要得到[1,2][2,3][3,4]而不是[1,2][3,4]如此2,3线段便不存在
}
void callen(int i)
{
if(a[i].s>0){//s>0(存在完全匹配才会使得s+=flag)说明该段区域已经完全被投影
a[i].len=a[i].mr-a[i].ml;
}else if(a[i].r-a[i].l==1){//s=0说明,说明该边已经不是完全被投影覆盖,但是它的子节点(更小的区域)可能被覆盖,所有考察子节点
a[i].len=0;//但如果形如[5,6]区间,不用查看子节点,因为他们就是叶节点,建立的时候区间范围最小就是1,不存在[5,5]
}else{
a[i].len=a[i*2].len+a[i*2+1].len;
}
return ;
}
void updata(int i,Node b)//I是线段树标号,从1开始表示从根节点开始遍历。NODE从1开始,表示从第一条线段开始
{
if(a[i].ml==b.y1&&a[i].mr==b.y2)
{
a[i].s+=b.flag;//一旦完全匹配某个线段树节点,将该节点s加上顶边底边权值(1或-1)。加1相当于从线段树里加上一条线段,-1相当于除去一条线段
callen(i);//这里的 callen(i)与下面的 callen(i)是一样的,表示update结束之后都要进行修改len的操作
return ;
}
if(b.y2<=a[i*2].mr) updata(i*2,b);//全部在左子树
else if(b.y1>=a[i*2+1].ml) updata(i*2+1,b);//全部在右子树
else//两边都有
{
Node temp=b;
temp.y2=a[i*2].mr;
updata(i*2,temp);
temp=b;
temp.y1=a[i*2+1].ml;
updata(i*2+1,temp);
}
callen(i);//如果存在UPDATE递归,说明父节点没有完全匹配,在子节点处理结束后,递归回来,使得父节点的len等于子节点len之和
return ;//可见线段树是一个不断向下的过程,向下之后需要向上。
}
int main()
{
// freopen("in.txt","r",stdin);
int n,t,p=1,te;
double x1,x2,y1,y2;
while(scanf("%d",&n),n)
{
t=1;
for(int i=0;i<n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
node[t].x=x1;//node按照左右方向
node[t].y1=y1;
node[t].y2=y2;
node[t].flag=1;
y[t++]=y1;
node[t].x=x2;
node[t].y1=y1;
node[t].y2=y2;
node[t].flag=-1;
y[t++]=y2;
}
sort(node+1,node+t,cmp);
sort(y+1,y+t);//y按照上下方向,离散化水平方向的线段范围,离散到[1,t-1]区域,同时标记离散之前对应的水平方向范围
build(1,1,t-1);
updata(1,node[1]);//首先投影第一条线段,即最左边的那条
double sum=0;
for(int i=2;i<t;i++)
{
sum+=a[1].len*(node[i].x-node[i-1].x);//a[1]即是根节点,最终的投影区域即是a[1].len
updata(1,node[i]);//这里的线段树建立在X,左右长度:node[i].x-node[i-1].x,上下长度:a[1].len
}
printf("Test case #%d\n",p++);
printf("Total explored area: %.2lf\n\n",sum);
}
return 0;
}