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的长度
好辛苦,终于写完了,希望对你有帮助!