poj Allantis (面积计算)1155线段…

来源:互联网 发布:java class 命名规范 编辑:程序博客网 时间:2024/06/06 00:43

http://poj.org/problem?id=1151

Atlantis
Time Limit: 1000MSMemory Limit: 10000KTotal Submissions: 12467Accepted: 4809

Description

There are several ancient Greektexts that contain descriptions of the fabled island Atlantis. Someof these texts even include maps of parts of the island. Butunfortunately, these maps describe different regions of Atlantis.Your friend Bill has to know the total area for which maps exist.You (unwisely) volunteered to write a program that calculates thisquantity.

Input

The input consists of severaltest cases. Each test case starts with a line containing a singleinteger n (1 <= n <= 100) ofavailable maps. The n following lines describe one map each. Eachof these lines contains four numbers x1;y1;x2;y2 (0<= x1 < x2 <= 100000;0<= y1 < y2 <= 100000),not necessarily integers. The values (x1; y1) and (x2;y2) are thecoordinates of the top-left resp. bottom-right corner of the mappedarea.
The input file is terminated by a line containing a single 0. Don'tprocess it.

Output

For each test case, your programshould output one section. The first line of each section must be"Test case #k", where k is the number of the test case (startingwith 1). The second one must be "Total explored area: a", where ais the total explored area (i.e. the area of the union of allrectangles in this test case), printed exact to two digits to theright of the decimal point.
Output a blank line after each test case.

Sample Input

210 10 20 2015 15 25 25.50

Sample Output

Test case #1Total explored area: 180.00 

Source

Mid-Central European Regional Contest 2000

题意:每张地图包含一个矩形,这些矩形相互叠加覆盖,最后形成的多边形的面积是多少。

题目分析:这题我们一般会自然想到一种非常朴素的算法,即每个矩形按照它框定的范围进行数据范围内每个单元格(本题是10w*10w)进行覆盖,最后算已被覆盖的总个数,即面积。但是本题的数据范围稍大,而矩形个数n<=100,非常小,所以我们可以进行离散化(其实也必须离散化,因为给的值均为实数)来计算单元格数,但是每个格都要扫描一次矩形序列,看是否被覆盖,所以复杂度为200*200*100,因此这样可以AC了。我们可以进一步地进行优化,用一颗二维线段树来维护矩阵覆盖的情况,二维线段树的建树时间为nlognlogn,最后点覆盖的查询情况为n^2logn,当然查询中如果有整段覆盖的前向推进优化复杂度同样会降到nlognlogn,所以最终算法的复杂度降为nlognlogn,但是若使用二维线段树对存储空间的要求会非常大,此题数据范围达到了10w,所以要用二维线段树必须进行离散化。二维线段树我们在做题时很少使用,因为维护非常复杂,空间开销太大,并且在复杂度上并不特别优于其他一些优化后的朴素算法,因此我们一般不考虑二维线段树算法。

那么此题还能够进一步优化么?当然可以,可以采用计算几何中的扫描线方法进行优化,以一个轴为扫描线轴,另一个轴为矩形覆盖轴,再辅以一维线段树进行覆盖优化,那么在离散化的基础上我们的算法复杂度就可以降为nlogn,当然此算法复杂度的常数项比较大。下面就讲解下什么是扫描线算法:

首先我们选择扫描轴,我们人眼的习惯在于在x轴从小到大进行图形扫描,所以我们就把x轴定位扫描轴,那么我们在读入矩形的时候就存下它们所有的竖边(每个矩形两条),然后按照x值从小到大进行排列。x轴的扫描线还有一个非常重要的注意点,要在竖边中区分矩形的左边和右边(建议一个标记为1,一个为-1,你会看到这样标记在后面的处理中很方便,不需要单独的删边操作),因为左边意味着一个矩形的开始,所以要把此边加入图形,右边意味着一个矩形的结束,所以要把此边对应的矩形剔除。下面考虑y轴,y轴首先进行离散化,然后进行线段树建树,建树的过程中要注意以下几点:1.我们保存的是矩形的边,是线段,所以建树时我们的终结条件是l+1==r,而不是传统的l==r,因为对于线段覆盖来说,单个节点毫无意义,只会带来麻烦,而且为保证线段的覆盖单一性和建树的纯粹和完整性,我们在递归建树时采用build(p<<1,l,mid)和build(p<<1|1,mid,r)的方法,将中间那个点成功避开,而不是传统的mid和mid+1,因为线段的边界可能重合。下面就是算法的核心了,扫描线的工作过程,首先说一点,离散化后的抽象值和实际值的对应转换你可以事先存于相应数据结构中,也可以利用抽象值来读取,视个人喜好。

算法过程:从第一条竖边到最后一条竖边,逐条插入线段树,并即时记录下此时整个区间中已经被覆盖的线段的实际长度(注意一定要是实际长度,在计算中用cover值来维护现在此线段是否还存在,还是已经被矩形右边删除,这个很重要),然后利用下一条竖边与当前竖边的x值差乘以现有的y轴覆盖实际长度就是此段覆盖的面积了,不明白的可以画个图稍加理解。算出所有竖边间的覆盖面积总和,即为结果。

 

 

 

线段树经典应用,求矩形并的面积
把每个矩形投影到 x 坐标轴上来(投影到 y 轴上去也是可以的,投影到 x 轴上去的话,就相当于矩形是竖着切的)
然后我们可以列举矩形的 x 坐标,然后检测当前相邻 x 坐标上 y 标的目标的合法长度,两种相乘就是面积
然后关键就是如何用线段树来保护那个 “合法长度”
线段树的节点如许定义
struct node { int left,right,cov; double len; }
cov 默示当前节点区间是否被覆盖,len 是当前区间的合法长度
然后我们经由过程“扫描线”的办法来进行扫描
列举 x 的竖边,矩形的左边那条竖边就是入边,右边那条就是出边了
然后把所有这些竖边遵守 x 坐标递增排序,每次进行插入操纵
因为坐标不必然为整数,是以须要进行离散化处理惩罚
每次插入时若是当前区间被完全覆盖,那么就要对 cov 域进行更新
入边 +1 出边 -1,更新完毕后断定当前节点的 cov 域是否大于 0
若是大于 0,那么当前节点的 len 域就是节点所覆盖的区间
不然,若是是叶子节点,则 len=0,若是内部节点,则 len=阁下儿子的 len 之和

 

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<queue>
#include<cstring>
#include<set>
#include<cmath>
#include<algorithm>
#define LL long long

using namespace std;
 
const int N=105;
struct node
{
    intl,r;
    doublelf,rf,width;
    intcover;
}mem[N*6];//线段树 lf rf 分别为边界 cover 为覆盖情况 width 为覆盖的宽度
struct poin
{
    double x;//记录x坐标
    doubley1,y2;  //y1,y2用来记录平行y轴的线段的两个端点,
    int k;//标记是左边的边还是右边的边,拆点 k为-1代表左点 1 代表右点
}point[N*2];
double Y[N*2];
bool cmp(poin a,poin b) //排序(x按照从小到大,只需要把x排序)
{
    returna.x<b.x;
}
void build(int x,int l,int r)//建树
{
   mem[x].l=l;
   mem[x].r=r;
   mem[x].lf=Y[l];
   mem[x].rf=Y[r];
   mem[x].cover=mem[x].width=0;
    if(l+1==r)//叶子结点
    return;
    intmid=(l+r)>>1;
   build(x*2,l,mid);//端点模型,不是格子模型
   build(x*2+1,mid,r);
}
void find(int x)//更新此处的覆盖宽度
{
   if(mem[x].cover>0) //完全覆盖(右点则要完全覆盖)
    {
       mem[x].width=mem[x].rf-mem[x].lf; //求出长度
       return ;
    }
   if(mem[x].l+1==mem[x].r) // 叶子节点,wiith 为 0
   mem[x].width=0;
   else//非叶子节点即为下面子区间的覆盖综合,注意由于插入的递归性保证了此等式的执行顺序的正确性
  mem[x].width=mem[x*2].width+mem[x*2+1].width;
}
void add(int x,int k)//加入一个点k的影响
{
   if(mem[x].lf>=point[k].y1&&mem[x].rf<=point[k].y2)//全部覆盖;//直接比较实际值,显示出了cover和标记为1,-1的作用
    {
       mem[x].cover+=point[k].k;
       find(x);
       return ;
    }
    intmid=(mem[x].l+mem[x].r)>>1;
   if(Y[mid]<=point[k].y1)//查看 需要怎样往下更新
    {
       add(x*2+1,k);
    }
 else if(Y[mid]>=point[k].y2)
    {
       add(x*2,k);
    }
 else
    {
       add(x*2,k);
       add(x*2+1,k);
    }
   find(x);
}
int main()
{
   //freopen("data.txt","r",stdin);
    int n;
    intT=0;
   while(scanf("%d",&n)!=EOF)
    {
       if(n==0)
       break;
       ++T;
       double x1,y1,x2,y2;
       int I=0;
  int i;
       for( i=1;i<=n;++i)
       {
           scanf("%lf %lf %lf%lf",&x1,&y1,&x2,&y2);
           point[I].x=x1; //把坐标映射到数组里(离散化)
           point[I].y1=y1;
           point[I].y2=y2;
           point[I].k=1;
           Y[I]=y1;
           ++I;
           point[I].x=x2;
           point[I].y1=y1;
           point[I].y2=y2;
           point[I].k=-1;
           Y[I]=y2;
           ++I;
       }
       sort(Y,Y+I); //纵坐标排序(小到大)
       sort(point,point+I,cmp);
       build(1,0,I-1); //建树
       add(1,0);
       double ans=0;
       for( i=1;i<I;++i)
       {
           //cout<<i<<endl;
           ans+=(mem[1].width*(point[i].x-point[i-1].x));//每次用两个x坐标差×   当前覆盖宽度
           //cout<<ans<<endl;
           add(1,i);//cout<<"TTT"<<endl;
       }
       printf("Test case #%d\n",T);
       printf("Total explored area: %.2f\n\n",ans);
    }
    return0;
}

原创粉丝点击