zoukankan      html  css  js  c++  java
  • poj1151 & hdu1542 Atlantis(扫描线+离散化+线段树)

    Atlantis
    Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u

    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 

    吐槽版区开始啦 :(PS: 欢迎各位高手,相互吐槽,商讨武林秘籍,增加内在修为 大笑

     —— >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>..

    吐槽L:加上今天,扫描线已经搞了两天了,本打算一上午就搞会了,没想到搞毁了。惊恐哎,我扫, 我扫, 我再扫。 扫来扫去,终于快“扫”完了哭。。。。。

    先说下,扫描线,这是个好东西啊,你说明明就是个求面积,用个尺子一量,咔擦咔擦,整吧整吧不就完了吗,但是好像没那么简单,数学中的思想,矩形只要求得长和宽,那么面积就迎刃而解了。说是很简单,但是如何用计算机编程来实现它呢?这确实需要思考一下,计算机是按指令来执行任务的,我不能想让它乘哪个边就乘哪个边,想让它加哪个编就加哪个边。所以这确实需要好好思考一下:那么问题就来了,我如何把这些边的信息存起来呢,对,可以用结构体,具体要存那些信息呢,以抓图为例:


    看图中的扫描线,此道题我是从左往右扫,需要存的信息是黑体实线的信息,把每条黑体实线的横坐标和上下顶点的纵坐标保存。 

     

     举个例子,存(10, 15)和 (10, 25)这条边的信息,

      那么我存的东西为       line[1].x = 10,                 

                                       line[1].y1 = 15,

                                       line[1].y2 = 25;

                                       line[1].f = 1(注意这里需要一个标记变量,来辨别此时这条边是否已经加入还是删除)。

    其它黑体实线也这样存。

    • 这样把每条黑体实线的横坐标和上下顶点的纵坐标存完后,就可以建立线段树(相信此时你已经了解线段树了),但是这里建的线段树和平时说的还是有区别的,比如要建一个区间1-4的线段树。如图所示:
    •                    
    •              

    但是这里要建的树和上面的还是要差别的。应该为:

                 

    这是为什么呢,为什么要按下面的方法建树,为什么是(l, mid, rt <<1)

                                                                                    (mid, r ,rt<<1 | 1)

                             而不是(l, mid, rt<<1)

                                        ( mid+1, r, rt<< 1 | 1)

    这是因为如果按普通方法建树,那么(2, 3)区间就没法表示了,我更新信息的时候,(2, 3)区间也得用到,所以要按第二种方法建树。


    建树这里也有讲究:这里要用到离散化,话说我上网一搜,搜到的东西没看不懂啊,这里用我自己的话说一下吧:你比如说我要以(10, 25.5)这个区间建树,我不会直接让根树的左区间==10, 右区间==25.5; 而是让左区间==1, 同时用个变量记录下10这个值, 让右区间==4,同时也用个变量记录一下25.5这个值。

       如图所示:

       此时可以定义结构体变量:

             来存这些信息 则圈2的信息应该这样存:T[1].l = 1; T[1].r

                                                                                                                                                  T[1].lf  =10; T[1].r = 25.5, 这样就可以了。 而c变量的作用是,判断当前边是否加入或者删除。


    而建树代码这里就不贴了,为下文中的build函数。


    说到这里,相信你已经把黑体实线的信息都存好了,而且根据这些信息,建立了一个特殊的线段树。

    然后你需要排一下序,对 y 数组和 line 结构体数组,按从小到大存。


    那么接下来应该怎么做呢?

    那么接下来,我就要开始扫描了,这里也是最难理解的。说是扫描其实也可以说是边的加入和删除,扫面线扫到这条边,且这条边的标记变量为1,则加边,都则去掉此边(好像说的有点难理解了,额额)。

    最难理解的部分,这里我想用代码讲解(代码有点挫。):

    void calen(int rt)
    {
         if(T[rt].c > 0)//如果这段被覆盖,就更新这段的长度为实际长度
         {
             T[rt].cnt = T[rt].rf-T[rt].lf;
             return ;
         }
         if(T[rt].l + 1 == T[rt].r)  T[rt].cnt = 0; //如果这段被撤销,而且是最后的就把长度变为0
         else T[rt].cnt = T[rt<<1].cnt + T[rt<<1|1].cnt;//如果被撤销但不是最后的,就加一下左右
    }
    
    void updata(int rt, Line e)//加入或者减去一条线段后的更新
    {
        if(e.y1==T[rt].lf && e.y2==T[rt].rf)
        {
            T[rt].c += e.f//改变覆盖情况
            calen(rt);//返回此区间所表示的时间长度
            return ;
        }
        if(e.y2<=T[rt<<1].rf) updata(rt<<1, e);//当你扫到的边的右端点不大于T[rt<<1].rf时,执行这条语句
        else if(e.y1>=T[rt<<1|1].lf) updata(rt<<1|1, e);//当你扫到的边的左端点不小于T[rt<<1|1].lf时,执行这条语句
        else//跨区间时,执行这条语句
        {
            Line temp;//把它分成左区间和右区间分别处理
            temp = e;
            temp.y2 = T[rt<<1].rf;
            updata(rt<<1, temp);
    
            temp = e;
            temp.y1 = T[rt<<1|1].lf;
            updata(rt<<1|1, temp);
        }
        calen(rt);//把根节点左孩子和右孩子的实际长度加起来存到此根结点上
    }

    最好能在纸上自己模拟一遍,比如把此题的第一条边加上,程序是怎么运行的。

    其实求得面积是一块一块加起来的(图明天补上,晚上宿舍熄灯了,没法拍了,,,,sad,,,,),而且应该深刻理解每条边标记变量为1, 2, 0,或则先增在减小的含义,如果能理解这两个地方,这道题应该差不多了。。。。

    好题难题不应该只做一遍,有空再做一遍,再来修改此篇题解。



    好,现贴代码如下:



    #include<stdio.h>
    #include<algorithm>
    #include <iostream>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    #define MAXN 400
    double y[MAXN];
    int n;
    
    using namespace std;
    
    struct Line{
        int f;
        double x, y1, y2;
    }line[MAXN];
    
    struct node{
        int l, r, c;
        double lf, rf, cnt;
    }T[MAXN<<2];
    
    
    bool cmp(Line a, Line b)
    {
        return a.x < b.x;
    }
    
    void build(int l, int r, int rt)
    {
        T[rt].l = l;
        T[rt].r = r;
        T[rt].cnt = T[rt].c = 0;
        T[rt].lf = y[l];
        T[rt].rf = y[r];
        if(l+1==r) return ;
        int mid=(l+r)>>1;
        build(l, mid, rt<<1);
        build(mid, r, rt<<1|1);
    }
    
    void calen(int rt)
    {
         if(T[rt].c > 0)
         {
             T[rt].cnt = T[rt].rf-T[rt].lf;
             return ;
         }
         if(T[rt].l + 1 == T[rt].r)  T[rt].cnt = 0;
         else T[rt].cnt = T[rt<<1].cnt + T[rt<<1|1].cnt;
    }
    
    void updata(int rt, Line e)
    {
        if(e.y1==T[rt].lf && e.y2==T[rt].rf)
        {
            T[rt].c += e.f;
            calen(rt);
            return ;
        }
        if(e.y2<=T[rt<<1].rf) updata(rt<<1, e);
        else if(e.y1>=T[rt<<1|1].lf) updata(rt<<1|1, e);
        else
        {
            Line temp;
            temp = e;
            temp.y2 = T[rt<<1].rf;
            updata(rt<<1, temp);
    
            temp = e;
            temp.y1 = T[rt<<1|1].lf;
            updata(rt<<1|1, temp);
        }
        calen(rt);
    }
    
    
    int main()
    {
        int i, cnt, k=1;
        double x1, y1, x2, y2, ans;
        while(~scanf("%d",&n)&&n)
        {
            cnt = 1;
            ans = 0;
            for(i=0; i<n; i++)
            {
                scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
                line[cnt].x = x1;
                line[cnt].y1 = y1;
                line[cnt].y2 = y2;
                line[cnt].f = 1;
                y[cnt++] = y1;
                line[cnt].x = x2;
                line[cnt].y1 = y1;
                line[cnt].y2 = y2;
                line[cnt].f = -1;
                y[cnt++] = y2;
            }
            cnt--;
            sort(y+1, y+cnt+1);
            sort(line+1, line+cnt+1, cmp);
    
            build(1, cnt, 1);
    
            updata(1, line[1]);
    
            for(i=2; i<=cnt; i++)
            {
                ans += T[1].cnt*(line[i].x-line[i-1].x);
                updata(1, line[i]);
            }
            printf("Test case #%d
    ", k++);
            printf("Total explored area: %.2lf
    
    ", ans);
        }
        return 0;
    }




    每天训练发现我比别人做的好慢,但是理解的更深刻,如果一开始学一个新知识点就搜模板,那么这样的人是走不远的,毕业之后带走的只有思维,什么荣誉,奖杯都已经不重要了。
  • 相关阅读:
    js数组与字符串的相互转换方法
    js页面跳转常用的几种方式
    js刷新页面方法大全
    IIS上开启反向代理实现Vue项目接口跨域处理
    【问题解决记录】vue解决低版本安卓与ios10以下系统兼容性问题
    【解决问题记录】https网站中请求http资源接口报错与netERRSSLPROTOCOLERROR错误的解决
    indexedDb数据库基本操作
    Object常用方法
    htmlToTex
    禁止鼠标右键保存/拖动/选中/复制 图片/文字
  • 原文地址:https://www.cnblogs.com/6bing/p/3931221.html
Copyright © 2011-2022 走看看