hdu Atlantis(线段树的面积并)

Problem Description
There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, 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 this quantity.
 

 

Input
The input consists of several test cases. Each test case starts with a line containing a single integer n (1 <= n <= 100) of available maps. The n following lines describe one map each. Each of 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 the coordinates of the top-left resp. bottom-right corner of the mapped area. 
The input file is terminated by a line containing a single 0. Don't process it.
 

 

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

 

Sample Input
2 10 10 20 20 15 15 25 25.5 0
 

 

Sample Output
Test case #1 Total explored area: 180.00
***************************************************************************************************************************
线段树的面积并,最重要的是对扫描线的理解,可根据x或y坐标建扫描线,对下线加入,上线删除(即入边和出边),每加入或删除一条线段时,就计算面积。
由此在计算时要对x坐标(y坐标)去重,还要对x坐标排序
具体理解:

顾名思义,扫描法就是用一根想象中的线扫过所有矩形,在写代码的过程中,这根线很重要。方向的话,可以左右扫,也可以上下扫。方法是一样的,这里我用的是由下向上的扫描法。

     

如上图所示,坐标系内有两个矩形。位置分别由左下角和右上角顶点的坐标来给出。上下扫描法是对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 }
View Code

 

转载于:https://www.cnblogs.com/sdau--codeants/p/3531305.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值