Atlantis
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 11823 Accepted Submission(s): 4990
The input file is terminated by a line containing a single 0. Don’t process it.
Output a blank line after each test case.
2 10 10 20 20 15 15 25 25.5 0
Test case #1 Total explored area: 180.00
题意:
求矩形面积的大小,重叠的部分只用计算一次即可
题解:
线段树+坐标离散化+扫描线+二分查找线段的左右端点
下面是转载他人的:
http://blog.csdn.net/u013480600/article/details/22548393 的解析
http://blog.csdn.net/xingyeyongheng/article/details/8927732 的代码
线段树的典型扫描线用法.
首先假设有下图两个矩阵,我们如果用扫描线的方法如何计算它们的总面积呢?
首先我们将矩形的上下边分为上位边(即y坐标大的那条平行于x轴的边),和下位边(y坐标小的平行于x轴的边).然后我们把所有矩形的上下位边按照他们y坐标从小到大排序,可以得到4条扫描线:
又因为上面2个矩形有4个不同的浮点数x坐标,所以我们需要把x坐标离散化,这样才能用线段树来维护信息.所以我们这样离散化:
由上图可知,4个不同的x坐标把x轴分成了3段有效的区间.这里要注意我们线段树中每个叶节点(控制区间[L,L])不是指X[L]坐标,而是指区间[X[L],X[L+1]].线段树中其他节点控制的区间[L,R],也是指的x坐标轴的第L个区间到第R个区间的范围,也就是X[L]到X[R+1]坐标的范围.
然后我们Y坐标从小到大的顺序读入每条扫描线,并维护当前我们所读入的所有扫描线能有效覆盖X轴的最大长度sum[1].这里特别要注意如果我们读入的扫描线是矩形的下位边,那么我们就使得该范围的标记cnt位+1,如果是上位边,那么该范围的cnt就-1.所以如果cnt=0时,表示该节点控制的范围没有被覆盖,只要cnt!=0 就表示该节点控制的几块区间仍然被覆盖.
下面依次读入每条矩阵边,来一一分析,首先是读入第一条矩阵边:
我们读入了矩形1的下位边,那么该区域的cnt就+1=1了,所以该区域[10,20]就被覆盖了,然后可以推出整个区域被覆盖的长度是10.再根据第二条扫描线离第一条扫描线的高度差为5.所以不管你第二条扫描线是哪个矩形的什么边,或者能覆盖到X轴的什么范围,我上图中蓝色的矩形面积肯定是要算到总面积里面去的.即总面积ret+=sum[1]*(扫描线2的高度-扫描线1的高度). (想想看是不是这样).
下面读第二条扫描线:
由于第二条扫描线也是下位边,所以[15,20]和[20,25]的cnt+1.使得我们覆盖的范围变成了[10,25]了,并且第3条扫描线在20高度,所以这次我们必然增加的面积是上面深蓝色的长条=sum[1]*(扫描线3的高度-扫描线2的高度).
下面我们要读第三条扫描线了:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX=200+10;
int mark[MAX<<2];///记录某个区间的下底边个数
double sum[MAX<<2];///记录某个区间的下底边总长度
double hashs[MAX];///对x进行离散化,否则x为浮点数且很大无法进行线段树
///以横坐标作为线段(区间),对横坐标线段进行扫描
///扫描的作用是每次更新下底边总长度和下底边个数,增加新面积
struct seg ///线段
{
double l,r,h;
int d;
seg() {}
seg(double x1,double x2,double H,int c):l(x1),r(x2),h(H),d(c) {}
bool operator<(const seg &a)const
{
return h<a.h;
}
} s[MAX];
void Upfather(int n,int left,int right)
{
if(mark[n])sum[n]=hashs[right+1]-hashs[left];///表示该区间整个线段长度可以作为底边
else if(left == right)sum[n]=0;///叶子结点则底边长度为0(区间内线段长度为0)
else sum[n]=sum[n<<1]+sum[n<<1|1];
}
void Update(int L,int R,int d,int n,int left,int right)
{
if(L<=left && right<=R) ///该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新
{
mark[n]+=d;///更新底边相差差个数
Upfather(n,left,right);///更新底边长
return;
}
int mid=(left+right)>>1;
if(L<=mid)Update(L,R,d,n<<1,left,mid);
if(R>mid)Update(L,R,d,n<<1|1,mid+1,right);
Upfather(n,left,right);
}
int search(double key,double* x,int n)
{
int left=0,right=n-1;
while(left<=right)
{
int mid=left+right>>1;
if(x[mid] == key)return mid;
if(x[mid]>key)right=mid-1;
else left=mid+1;
}
return -1;
}
int main()
{
int n,num=0;
double x1,x2,y1,y2;
freopen("in.txt","r",stdin);
while(scanf("%d",&n)&&n)
{
int k=0;
for(int i=0; i<n; ++i)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
hashs[k]=x1;
s[k++]=seg(x1,x2,y1,1);
hashs[k]=x2;
s[k++]=seg(x1,x2,y2,-1);
}
///从小到大排序
sort(hashs,hashs+k);///对扫描线的y坐标进行排序
sort(s,s+k);
int m=1;///若有8个端点,则有7段,即七个叶子节点
for(int i=1; i<k; ++i) ///去重复端点
if(hashs[i] != hashs[i-1])hashs[m++]=hashs[i];
double ans=0;
for(int i=0; i<k; ++i) ///扫描线段,二分查找此扫描线的左右端点
{
int L=search(s[i].l,hashs,m);
int R=search(s[i].r,hashs,m)-1;
Update(L,R,s[i].d,1,0,m-1);///扫描线段时更新底边长度和底边相差个数
ans+=sum[1]*(s[i+1].h-s[i].h);///新增加面积
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",++num,ans);
}
return 0;
}