HDU1542-Atlantis【离散化&线段树&扫描线】个人认为很全面的详解

刚上大一的时候见过这种题,感觉好牛逼哇,这都能算

如今已经不打了,不过适当写写题保持思维活跃度还是不错的,又碰到这种题了,想把它弄出来

说实话,智商不够,看了很多解析,花了4、5个小时才弄明白

网上好多都是直说一半,弄得我很难受,需要查看很多题解不断对比才清楚

首先线段树这玩意,不光是线段树吧,只要牵扯到递归都很抽象,要想好久

如果中途有哪些不懂,继续看,代码我尽量做到每一行都有注释

1.离散化

先说离散化,这里面牵扯到小数,而线段树是维护一个整数区间,这是我们首先遇到的问题

比如这种情况,第二个矩形刚好多了0.5怎么办哦

这时候就要用离散化来处理了,我个人感觉离散化说白了就是映射,先把一些难算的数值映射到一些简单的数值上算完了再换回去

比如解物理题的时候先把一些不能拆开但很复杂的表达式用符号代替,算完了再换回去

那么两个矩形四条竖边,对应的x分别是10、15、20、25.5,那么久把这个不同的x值,存到数组里dif_x[4] = {10,15,20,25.5}

用的时候,比如你要用到15~25.5这条边的时候就用dif_x[3] - dif_x[1]得到10.5,就ok了

2.扫描线

你就想象有一根水平的线从下往上走,碰到边就更新,这个有前人说的很详细,我就直接拿来用了

http://www.cnblogs.com/Konjakmoyu/p/6050343.html

这个讲的很好,我一开始理解扫描线就是看的这个

3.线段树

首先你要有线段树的基本知识嘛,这个就不说了

说一下里面值的定义

struct node2
{
    int l,r,cnt;
    double len;
}tree[1000];

这个也是这道题里面最难理解的一部分

线段树到底维护的什么,或者说存放的什么?

首先,根据扫描线的思路,你要存放的是当前扫描线的长度,这样才能乘高度差

那么你每次要更新的就是蓝线的长度

那么蓝线的长度是不是可以由 dif_x[r] - dif_x[l]得到

比如我们第一次更新的这条蓝线是10~20,那么是不是可以由dif_x[] 数组的下标0~2来表示

第二次更新的蓝线是10~25.5,是不是可以由0~3来表示

最后一次由1~3表示

那我们就知道了,线段树里的l,r其实存的是该结点所能涉及的dif_x[]数组的下标

结点1的l,r分别是0,3那么就是10——25.5这条线段中,被覆盖的长度

但是这里就有一个问题了,0——1的长度可以理解,但是0是一个点,它的长度就是0啊

所以我么规定,每一个坐标表示从它到它+1的点的长度

比如tree[4]表示,0~1的长度是5

tree[6]表示,2~3的长度是5.5

cnt表示该结点是否被覆盖,参与这次面积的计算

double len 当然表示的就是该线段的长度了

补充

还需要一个比较重要的结构体

struct node
{
    double x1,x2,y;
    int flag;
    void init(double l,double r,double h,int key) {
        x1 = l; x2 = r; y = h; flag = key;
    }
}line[203];

这个结构体代表每一根横线

比如灰线代表line[0],红线line[1],粉线line[2]以此类推

x1,x2就是这根线的左右端点坐标,y是这根线的y轴坐标,flag表示这根线是矩形的下面的线还是上面的线

这个很重要,如果是下面的线,在更新tree[]的时候,由于你是从下往上扫,所以要+1

如果是上面的线,在更新线段树tree[]的时候,由于你是从下往上扫,所以要-1,表示你已经扫完了某个矩形,你要将矩形的上边减掉

要讲的就这么多了吧

看代码

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 using namespace std;
  5
  6 double dif_x[203];//记录不同的x坐标
  7
  8 struct node
  9 {
 10     double x1,x2,y;
 11     int flag;//各个参数上面说过
 12     void init(double l,double r,double h,int key) {
 13         x1 = l; x2 = r; y = h; flag = key;
 14     }
 15 }line[203];
 16
 17 bool cmp(node a, node b)
 18 {
 19     return a.y < b.y;
 20 }
 21
 22 struct node2
 23 {
 24     int l,r,cnt;//各个参数上面说过
 25     double len;
 26 }tree[1000];
 27
 28 void build_tree(int id,int l,int r)//最基本的线段树建树不多说
 29 {
 30     tree[id].l = l;
 31     tree[id].r = r;
 32     tree[id].cnt = 0;
 33     tree[id].len = 0;
 34     if(l==r){
 35         return;
 36     }
 37     int mid = (r + l)>>1;
 38     build_tree(id<<1,l,mid);
 39     build_tree((id<<1)+1,mid+1,r);
 40 }
 41
 42 void getlen(int id)
 43 {
 44     if(tree[id].cnt >= 1) { //如果该段被覆盖那么就直接由dif_x数组获得长度
 45         tree[id].len = dif_x[tree[id].r+1] - dif_x[tree[id].l];//看了注释①以后,应该不难理解了
 46     }
 47     else {
 48         tree[id].len = tree[id<<1].len + tree[(id<<1)|1].len;//如果没有被覆盖,那么应该是由左右孩子的和
 49     }
 50 }
 51
 52 void update(int id,int l,int r,int v)
 53 {
 54     if(tree[id].l==l && tree[id].r==r) {//目标区间
 55         tree[id].cnt += v;//标记是否覆盖
 56         getlen(id);//算一下长度
 57         return;
 58     }
 59     int mid = (tree[id].l+tree[id].r)>>1;
 60     if(r <= mid){
 61         update(id<<1,l,r,v);//更新左子树
 62     }
 63     else if(l > mid) {
 64         update((id<<1)+1,l,r,v);//更新右子树
 65     }
 66     else {
 67         update(id<<1,l,mid,v);
 68         update((id<<1)+1,mid+1,r,v);//更新左右子树
 69     }
 70     getlen(id);//push_up一下
 71 }
 72
 73 int mySearch(double p, int l, int r)//二分找p在dif_x数组中的下标
 74 {
 75     while(l <= r)
 76     {
 77         int mid = (l + r)>>1;
 78         if(dif_x[mid] == p){
 79             return mid;//返回这个下标
 80         }
 81         if(dif_x[mid] < p) {
 82             l = mid + 1;
 83         }
 84         else {
 85             r = mid - 1;
 86         }
 87     }
 88 }
 89
 90 int main()
 91 {
 92     int n,noc = 0;//number_of_case
 93     while(~scanf("%d",&n))//输入
 94     {
 95         if (n == 0) break;
 96         noc ++;
 97         double x1,y1,x2,y2;//矩形的位置参数
 98         int line_num = 0;//一共有多少条横线
 99         for(int i=0;i<n;i++)//输入
100         {
101             scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
102             line[line_num].init(x1,x2,y1,1);//下面的线,flag = 1
103             dif_x[line_num++] = x1;//数据中一共有多少个不同的横坐标
104             line[line_num].init(x1,x2,y2,-1);//上面的线,flag = -1
105             dif_x[line_num++] = x2;
106         }
107         sort(line, line+line_num, cmp);//对横线由低到高进行排序,因为你是从下往上扫描的
108         sort(dif_x, dif_x+line_num);//对dif_x去重,要先排个序,这样更方便
109         int dif_x_num = unique(dif_x, dif_x+line_num) - dif_x;//dif_x_num表示去重后不同的x坐标的数量
110         build_tree(1,0,dif_x_num-1);//建立线段树
111         double ans = 0;//最终的答案
112         for(int i=0;i<line_num-1;i++)//开始扫描
113         {
114             int line_l = mySearch(line[i].x1,0,dif_x_num-1);//第i根线的左端点对应在dif_x的下标
115             int line_r = mySearch(line[i].x2,0,dif_x_num-1)-1;//右边要减一,看注释①
116             update(1,line_l,line_r,line[i].flag);//更新线段树
117             ans += tree[1].len * (line[i+1].y - line[i].y);//求面积,tree[1]就是总长度嘛
118         }
119         printf("Test case #%d\n",noc);
120         printf("Total explored area: %.2lf\n\n",ans);
121     }
122 }
注释① 这里我看了n多题解,没一个说为什么-1,管这个我就懵逼了70%的时间,希望看了这个会节约你很多时间其实我前面已经说了,每个点代表从它到它+1的点的长度,那么你要求0~3的长度,其实要求的是0~2的长度

好辛苦,终于写完了,希望对你有帮助!
时间: 2024-10-28 16:53:37

HDU1542-Atlantis【离散化&线段树&扫描线】个人认为很全面的详解的相关文章

hdu1542 Atlantis (线段树+扫描线+离散化)

Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 9032    Accepted Submission(s): 3873 Problem Description There are several ancient Greek texts that contain descriptions of the fabled i

hdu-1542 Atlantis(离散化+线段树+扫描线算法)

题目链接: Atlantis Time Limit: 2000/1000 MS (Java/Others)     Memory Limit: 65536/32768 K (Java/Others) Problem Description There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include ma

HDU-1542 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. You

HDU 4419 Colourful Rectangle --离散化+线段树扫描线

题意: 有三种颜色的矩形n个,不同颜色的矩形重叠会生成不同的颜色,总共有R,G,B,RG,RB,GB,RGB 7种颜色,问7种颜色每种颜色的面积. 解法: 很容易想到线段树扫描线求矩形面积并,但是如何维护每种颜色的长度着实让我伤透了脑筋.后来看了一位朋友的题解,才幡然醒悟. 开始想到了用二进制表示颜色,R用001表示,G用010表示,B用100表示.那么就可以用十进制1~7表示7种不同颜色了. 维护 cov[rt][1~3] 表示此区间内3种原色各有多少个, Len[rt][i]表示每种颜色的长

HDU 1542 Atlantis(线段树扫描线+离散化求面积的并)

Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 11551    Accepted Submission(s): 4906 Problem Description There are several ancient Greek texts that contain descriptions of the fabled

POJ 1151 Atlantis(线段树 + 扫描线)

转载请注明原文:http://www.cnblogs.com/burning-flame/p/5934653.html 题目链接:http://poj.org/problem?id=1151 题意: 给你 n 个矩形的对角线坐标,求 n 个矩形并集的面积. 做法: 扫描线 + 线段树. 因为作线段树的题遇到了扫描线,只是粗浅的了解了一下. 就像字面上的:线性扫描一遍,对于每个单元,因为某些事件的发生会有一些变化. 举个例子: 现有长度为 n 的数组a,同时有 n 个区间覆盖[li, ri],对于

线段树区间更新操作及Lazy思想(详解)

此题题意很好懂:  给你N个数,Q个操作,操作有两种,‘Q a b ’是询问a~b这段数的和,‘C a b c’是把a~b这段数都加上c. 需要用到线段树的,update:成段增减,query:区间求和 介绍Lazy思想:lazy-tag思想,记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率. 在此通俗的解释我理解的Lazy意思,比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果

杭电 HDU ACM 1225 Atlantis (线段树 扫描线 离散化 最基本)

acm第一发扫描线问题,其实算法原理很好理解 ,但是具体实现起来还是遇到诸多问题,我尝试参考了网上两份对于解决 线段树表示区间问题的方法, 第一种是每个结点的真实值,比如对于更新离散化后的1 ~ 4区间,我们在线段树中更新的是1 ~ 3,这样单个结点也可以表示一个最小单位区间. 第二种那就是在建树的时候改变通常策略,1 ~ 10 为总区间,两个孩子为1 ~ 5 ,5 ~ 10. 核心难点:当我们每次找到需要更新的区间,首先应该更新cover值,然后判断此时是被覆盖了,还是解除覆盖了,如果刚好被覆

HDU 1542 Atlantis(线段树扫描线&#183;面积并)

题意  给你一些矩形的左下和右上的坐标  求这些矩形的面积并 最基础的扫描线  理解了就是个水题了  先看一些图吧                                恩  看完了有什么感觉没有  那些红色的线就可以当作传说中的扫描线  就像从左到右扫描嘛  可以发现  矩形有竖直边的地方就有这些线  这些线把把拼在一起的矩形切成了一个个的小矩形  我们把这些小矩形的面积加起来不就是要求的面积吗 那么现在这些小矩形的面积怎么求呢  长乘宽嘛  长是什么  两条红线之间的距离  恩  我