zoukankan      html  css  js  c++  java
  • hdu 1542 Atlantis

    线段树求矩形面积并

    经典题目,poj 1151 是相同的题目。终于学了求矩形面积并,详细说一下。

    首先是看小hh的线段树专题,因为找不到什么论文来看所以只好啃他的代码,啃了一个晚上,有感觉,但是不确定,只能轻轻体会到扫描线的意义。后来啃不下去了,就自己想,给想了出来,但是想出来居然是跟原始的方法不同的。所以下面说的是原始的方法(或者说是小hh代码中的方法),以及我自己想出来的一种方法,两种虽然不同,但是个人感觉本质还是差不多的,不过从效率上看,小hh的那种代码应该效率更高。另外下面给出的代码都是用线段树来模拟扫描法,其实还有更好的方法就是用DP的思想去优化,据说效率提高不是一点两点而是很多,但是还没学,学完会继续更新

    分析:

    1.矩形比较多,坐标也很大,所以横坐标需要离散化(纵坐标不需要),熟悉离散化后这个步骤不难,所以这里不详细讲解了,不明白的还请百度

    2.重点:扫描线法:假想有一条扫描线,从左往右(从右往左),或者从下往上(从上往下)扫描过整个多边形(或者说畸形。。多个矩形叠加后的那个图形)。如果是竖直方向上扫描,则是离散化横坐标,如果是水平方向上扫描,则是离散化纵坐标。下面的分析都是离散化横坐标的,并且从下往上扫描的

       扫描之前还需要做一个工作,就是保存好所有矩形的上下边,并且按照它们所处的高度进行排序,另外如果是上边我们给他一个值-1,下边给他一个值1,我们用一个结构体来保存所有的上下边 

    struct segment
    {
    double l,r,h;   //l,r表示这条上下边的左右坐标,h是这条边所处的高度
    int f;   //所赋的值,1或-1
    }

    接着扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。还记得给他们赋的值1或-1吗,下边是1,扫描到下边的话相当于往总区间插入一条线段,上边-1,扫描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,那会不会为负数呢?是不可能的,可以自己思考一下)。

    每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,就能得到最后的面积

    (这个过程其实一点都不难,只是看文字较难体会,建议纸上画图,一画即可明白,下面献上一图希望有帮组)

    从这个图,也可以感受到,就好比一个畸形的容器,往里面倒水,从最下面往上面涨,被水淹过的部分其实就是我们要求的面积

    下面给出代码

    /*
    1.保存矩形的上下边界,并且重要的,记录他们是属于上还是下,然后按高度升序排序
    2.保存竖线坐标,并且去重,是为了离散化
    3.以保存的上下边界数组去更新
    */
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define INF 0x3f3f3f3
    #define MAX 110
    #define LCH(i) ((i)<<1)
    #define RCH(i) ((i)<<1 | 1)
    
    struct segment //保存矩形上下边界
    {
      double l,r,h; //左右横坐标,纵坐标
      int f; //-1为下边界,1为上边界
    }ss[2*MAX];
    struct node //线段树节点
    {
      int l,r;
      int cnt; //该节点被覆盖的情况
      double len; //该区间被覆盖的总长度
      int mid()
      { return (l+r)>>1; }
    }tt[2*MAX*4];
    double pos[2*MAX];
    int nums;
    
    int cmp(struct segment a ,struct segment b)
    {
      return a.h<b.h;
    }
    
    void build(int a, int b ,int rt)
    {
     tt[rt].l=a; tt[rt].r=b; tt[rt].cnt=0; tt[rt].len=0;
     if(a==b) return ;
     int mid=tt[rt].mid();
     build(a,mid,LCH(rt));
     build(mid+1,b,RCH(rt));
    }
    
    int binary(double key ,int low, int high)
    {
       while(low<=high)
       {
          int mid=(low+high)>>1;
          if(pos[mid] == key) return mid;
          else if(key < pos[mid]) high=mid-1;
          else                    low=mid+1;
       }
       return -1;
    }
    
    void get_len(int rt)
    {
       if(tt[rt].cnt) //非0,已经被整段覆盖
          tt[rt].len = pos[tt[rt].r+1] - pos[tt[rt].l];
       else if(tt[rt].l == tt[rt].r) //已经不是一条线段
          tt[rt].len = 0;
       else //是一条线段但是又没有整段覆盖,那么只能从左右孩子的信息中获取
          tt[rt].len = tt[LCH(rt)].len + tt[RCH(rt)].len ;
    }
    
    void updata(int a, int b ,int val ,int rt)
    {
       if(tt[rt].l==a && tt[rt].r==b) //目标区间
       {
          tt[rt].cnt += val; //更新这个区间被覆盖的情况
          get_len(rt);  //更新这个区间被覆盖的总长度
          return ;
       }
       int mid=tt[rt].mid();
       if(b<=mid) //只访问左孩子
          updata(a,b,val,LCH(rt));
       else if(a>mid) //只访问有孩子
          updata(a,b,val,RCH(rt));
       else //左右都要访问
       {
          updata(a,mid,val,LCH(rt));
          updata(mid+1,b,val,RCH(rt));
       }
       get_len(rt); //计算该区间被覆盖的总长度
    }
    
    int main()
    {
      int Case=0;
      int n;
      while(scanf("%d",&n)!=EOF && n)
      {
        nums=0;
        for(int i=0; i<n; i++)
        {
          double x1,y1,x2,y2;
          scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
          ss[nums].l=x1;  ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=1;
          //记录上边界的信息
          ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=-1;
          //记录下边界的信息
          pos[nums]=x1; pos[nums+1]=x2;
          //记录横坐标
          nums += 2;
    
        }
    
        sort(ss,ss+nums,cmp); //横线按纵坐标升序排序
        sort(pos,pos+nums); //横坐标升序排序
        //for(int i=0; i<nums; i++) printf("%.2lf %.2lf  %.2lf\n",ss[i].l,ss[i].r,ss[i].h);
        int m=1;
        for(int i=1; i<nums; i++)
          if(pos[i]!=pos[i-1]) //去重
            pos[m++]=pos[i];
    
        build(0,m-1,1);  //离散化后的区间就是[0,m-1],以此建树
        double ans=0;
        for(int i=0; i<nums; i++) //拿出每条横线并且更新
        {
           int l=binary(ss[i].l,0,m-1);
           int r=binary(ss[i].r,0,m-1)-1;
           updata(l,r,ss[i].f,1); //用这条线段去更新
           ans += (ss[i+1].h-ss[i].h)*tt[1].len;
           //printf("%.2lf\n",ans);
        }
        printf("Test case #%d\n",++Case);
        printf("Total explored area: %.2f\n\n",ans);
      }
      return 0;
    }

    ————————————————————————————————————————————————————————————————————————————

    下面说一下我自己理解出来的一个方法,当时是还没有明白上面的代码及其思想的时候想出来的

    1.离散化横坐标,从下往上扫描上下边,一样要排序,一样给下边赋值1,上边赋值-1

    2.没扫描到一条上下边,把它投影到总区间,但不是算总区间被覆盖的总长度。而是这条边界投影后,看这条边界对应的区间内,哪些部分对应的值变为了0(那个1和-1叠加后会变回0),变为0的部分就可以乘上高度差得到一小块的面积

    这种方法还要记录一个值,就是总区间上每一段对应的最低高度,当某一段没有被线段覆盖时,它的最低高度是0,如果一旦被一个边界覆盖了,它的最低高度就是这条边界的高度(而且可以知道这个边界一定是下边界,不会是上边界首先覆盖的,这个道理和上面的一样),而已经被覆盖的线段,如果再给其他边界覆盖,无论是增加还是消除,其最低高度都不变。除非是完全消掉,那么它的最低高度又变回0

    这个过程其实也不难理解的,但是文字真心难理解,建议自己画图,很容易明白,下面再献上一图,希望有帮助

    下面给出两个代码,都是实现上面的思想的,第一种用了LAZY思想,效率比第二个高,第二个是不加思索地深入到每一片叶子再求面积。但是在oj都跑出了0ms,只能说数据水了。。。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define MAX 110
    #define LCH(i) ((i)<<1)
    #define RCH(i) ((i)<<1|1)
    
    struct segment
    {
       double l,r,h;
       int f;
    }ss[2*MAX];
    struct node
    {
       int l,r;
       double h;
       int cnt;
       int mid()
       { return (l+r)>>1; }
    }tt[2*MAX*4];
    double pos[2*MAX];
    double ans;
    int nums;
    
    int cmp(struct segment a ,struct segment b)
    {
       return a.h<b.h;
    }
    
    void build(int a ,int b ,int rt)
    {
       tt[rt].l=a; tt[rt].r=b; tt[rt].h=0; tt[rt].cnt=INF;
       if(a==b) return ;
       int mid=tt[rt].mid();
       build(a,mid,LCH(rt));
       build(mid+1,b,RCH(rt));
    }
    
    int binary(double key ,int low, int high)
    {
       while(low<=high)
       {
          int mid=(low+high)>>1;
          if(pos[mid] == key) return mid;
          else if(key < pos[mid]) high=mid-1;
          else                    low=mid+1;
       }
       return -1;
    }
    
    /*
    一个线段进来,是将该线段对应的党员的值都增加val,而不是变为val
    所以并不是找到目标区间就停止了,而在找到了目标区间的基础上,还要保证该区间各单元的值都相等
    那么才可以成段更新,因此找到了目标区间还要继续深入(而且可知深入进去都必定是目标区间的子区间)
    在最后找到了可改变值的区间时,就进去求面积函数
    */
    
    void cal(int val ,int rt ,int n)
    {
       if(tt[rt].cnt + val == 0) //可以计算面积
       {
          ans += (pos[tt[rt].r+1]-pos[tt[rt].l])*(ss[n].h-tt[rt].h);
          tt[rt].cnt=0;
       }
       else if(tt[rt].cnt == 0 && val==-1) //加入底线
       {
          tt[rt].h=ss[n].h;
          tt[rt].cnt=-1;
       }
       else if(tt[rt].cnt==INF)
       {
          tt[rt].cnt = val;
          tt[rt].h=ss[n].h;
       }
       else
          tt[rt].cnt+=val;
       return ;
    }
    
    void updata(int a ,int b ,int val ,int rt , int n)
    {
       int mid;
       if(tt[rt].l==a && tt[rt].r==b) //找到了目标区间但是还不能改变区间值
       {
          if(tt[rt].cnt!=INF || tt[rt].l==tt[rt].r) //整段的值都是一样的,可以更新了
          {
             cal(val,rt,n); //先进入求面积函数
          }
          else //整段的值不同,那么还要继续深入
          {
             mid=tt[rt].mid();
             updata(a,mid,val,LCH(rt),n);
             updata(mid+1,b,val,RCH(rt),n);
          }
          return ;
       }
       mid=tt[rt].mid();
       if(tt[rt].cnt!=INF) //当前区间的数值是统一的,要传递给左右孩子
       {
          tt[LCH(rt)].cnt=tt[RCH(rt)].cnt=tt[rt].cnt;
          tt[rt].cnt=INF;
       }
       if(b<=mid) //左孩子
          updata(a,b,val,LCH(rt),n);
       else if(a>mid)
          updata(a,b,val,RCH(rt),n);
       else
       {
          updata(a,mid,val,LCH(rt),n);
          updata(mid+1,b,val,RCH(rt),n);
       }
    }
    
    int main()
    {
       int Case=0;
       int n;
       while(scanf("%d",&n)!=EOF && n)
       {
          double x1,x2,y1,y2;
          nums=0;
          for(int i=0; i<n; i++)
          {
             scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
             ss[nums].l=x1; ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=-1;
             ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=1;
             pos[nums]=x1; pos[nums+1]=x2;
             nums += 2;
          }
          sort(ss,ss+nums,cmp);
          sort(pos,pos+nums);
          int m=1;
          for(int i=1; i<nums; i++)
             if(pos[i]!=pos[i-1])
                pos[m++]=pos[i];
          build(0,m-1,1);
          ans=0;
          for(int i=0; i<nums; i++) //拿出每条横线并且更新
          {
           int l=binary(ss[i].l,0,m-1);
           int r=binary(ss[i].r,0,m-1)-1;
           updata(l,r,ss[i].f,1,i); //用这条线段去更新
           //printf("%.2lf\n",ans);
          }
          printf("Test case #%d\n",++Case);
          printf("Total explored area: %.2lf\n\n",ans);
       }
       return 0;
    }
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define MAX 110
    
    int LCH(int n)
    { return n<<1; }
    int RCH(int n)
    { return n<<1|1; }
    
    struct segment
    {
       double l,r,h;
       int f;
    }ss[2*MAX];
    struct node
    {
       int l,r;
       double h;
       int cnt;
       int mid()
       { return (l+r)>>1; }
    }tt[2*MAX*4];
    double pos[2*MAX];
    double ans;
    int nums;
    
    int cmp(struct segment a ,struct segment b)
    {
       return a.h<b.h;
    }
    
    void build(int a ,int b ,int rt)
    {
       tt[rt].l=a; tt[rt].r=b; tt[rt].h=0; tt[rt].cnt=0;
       if(a==b) return ;
       int mid=tt[rt].mid();
       build(a,mid,LCH(rt));
       build(mid+1,b,RCH(rt));
    }
    
    int binary(double key ,int low, int high)
    {
       while(low<=high)
       {
          int mid=(low+high)>>1;
          if(pos[mid] == key) return mid;
          else if(key < pos[mid]) high=mid-1;
          else                    low=mid+1;
       }
       return -1;
    }
    
    void cal(int val ,int rt ,int n)
    {
       if(tt[rt].cnt==0 && val==-1)
       {
          tt[rt].cnt = val;
          tt[rt].h=ss[n].h;
          return ;
       }
       if(tt[rt].cnt + val == 0)
       {
          ans += (pos[tt[rt].r+1]-pos[tt[rt].l])*(ss[n].h-tt[rt].h);
          tt[rt].cnt=0;
          tt[rt].h=0;
          return ;
       }
       tt[rt].cnt += val;
       return ;
    }
    
    void updata(int a ,int b ,int val ,int rt ,int n) //一直更新到叶子
    {
       if(tt[rt].l == tt[rt].r) //到达叶子
       {
          cal(val,rt,n);
          return ;
       }
       int mid=tt[rt].mid();
       if(b<=mid) //左孩子
          updata(a,b,val,LCH(rt),n);
       else if(a>mid) //右孩子
          updata(a,b,val,RCH(rt),n);
       else
       {
          updata(a,mid,val,LCH(rt),n);
          updata(mid+1,b,val,RCH(rt),n);
       }
    }
    
    int main()
    {
       int Case=0;
       int n;
       while(scanf("%d",&n)!=EOF && n)
       {
          double x1,x2,y1,y2;
          nums=0;
          for(int i=0; i<n; i++)
          {
             scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
             ss[nums].l=x1; ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=-1;
             ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=1;
             pos[nums]=x1; pos[nums+1]=x2;
             nums += 2;
          }
          sort(ss,ss+nums,cmp);
          sort(pos,pos+nums);
          int m=1;
          for(int i=1; i<nums; i++)
             if(pos[i]!=pos[i-1])
                pos[m++]=pos[i];
          build(0,m-1,1);
          ans=0;
          for(int i=0; i<nums; i++) //拿出每条横线并且更新
          {
           int l=binary(ss[i].l,0,m-1);
           int r=binary(ss[i].r,0,m-1)-1;
           updata(l,r,ss[i].f,1,i); //用这条线段去更新
           //printf("%.2lf\n",ans);
          }
          printf("Test case #%d\n",++Case);
          printf("Total explored area: %.2f\n\n",ans);
       }
       return 0;
    }
  • 相关阅读:
    TextBox 只有下划线
    can't find web control library(web控件库)
    DropDownListSalesAC”有一个无效 SelectedValue,因为它不在项目列表中。
    IDE、SATA、SCSI、SAS、FC、SSD 硬盘类型
    如何打印1px表格
    CSS控制打印 分页
    Virtual Server could not open its emulated Ethernet switch driver. To fix this problem, reenable the Virtual Server Emulated Et
    Xml中SelectSingleNode方法中的xpath用法
    热带水果莫入冰箱?水果存放冰箱大法
    探索Asp.net的Postback机制
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2972808.html
Copyright © 2011-2022 走看看