Poj1151——Atlantis(Acwing247.亚特兰蒂斯)
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
扫描线 + 线段树 + 离散化 + 二分
我的第一道扫描线,而且我觉得这题挺难也挺麻烦的,所以记录一下也方便以后复习。
poj上的n范围比较小只有100,其实这道题用扫描线的话可以把n的范围扩到1e5的级别(可以去Acwing上提交https://www.acwing.com/problem/content/249/ 还有中文题面)
题目意思在平面对给我们一些矩形(输入矩形的左上角顶点和右上角顶点来确定),让我们统计出这些矩形的公共面积。
那么可以用扫描线来做。我们用一条平行于y轴的直线从第一个点开始向右边平移,面积的做法其实可以用到微积分的思想,其实就是两个相邻的x乘上每一个区间内线段的长度h,这个h我们可以用线段树来维护。
怎么来维护呢,因为在线进行的扫描过程中,对于同一个矩形,肯定会经过两次它的边,第一次是进入这个矩形区域,第二次是离开这个矩形区域,对于每一个矩形,我们在读入的时候定义它的左边的权值为1,右边的权值为-1,
这样每次modify之后线段树内大于0的区间就是我们要求的h, 那么就可以在线段树内维护一个cnt(就是lazy表记,维护这个节点的区间内长度是否大于0,如果大于就要加,如果等于0就不加)和len记录这个节点的区间长度。(扫描线思想,好巧妙啊!)
(yxc大佬的图,非常形象)
那么问题就转换成了一个用线段树区间修改+区间查询的问题。但是这样的话lazy比较非常难写,这个时候我们可以用到扫描线的性质,不需要下方lazy。因为线段树中下放lazy的操作都是在分裂区间时才会把当前的lazy pushdown到子节点,而扫描线问题的query和modify都不会分裂节点,所以不需要pushdown。
证明:1、query时我们要得到的是整个区间的len,即就是根节点的len,不需要分裂。
2、modify时由于对于每一个矩形,带权值的线段是成对出现的,所以也不需要分裂。(这点比较难理解且比较特殊,可以直接记住,一些扫描线问题的性质)
那么这题就转换成了不用下方lazy的线段树,敲起来就会轻松一点。(虽然不用这个性质硬刚直接敲pushdown也可以实现,但是写起来会很麻烦,yysy这题不下方lazy都已经很难敲了啊!)
需要注意的地方有:1、我们读进来的是点,而线段树维护的是区间,所以在build和modify的时候都要注意把区间需要处理一下。
2、本题的坐标存在浮点数,那我们就必须要用到离散化 + 二分来记录坐标。
还有一些小坑点:本题最后的输出要额外输出一个空行(出题人真的是丧心病狂不做人啊)以及敲得时候真的很烦(本蒻屐代码能力tcl)
总结: 昨天晚上开始看的这题,看了yxc大佬的讲解之后恍然大悟觉得自己知道了,但是上机敲发现还有很多细节问题很难解决(比如lazy的下方写起来很烦、pushup也不好写、线段树维护的区间和存储的点的关系需要转换)又弱弱的去看yxc大佬的讲解
看完之后还是似懂非懂(tcl),困得不行就直接睡觉了。今天白天和朋友去爬仁皇山,仿佛久违的运动让我的脑袋稍微转的快了一点?(居家隔离14天我是真的要睡傻了) 晚上把这题补了。第一道扫描线,纪念一下,防止过段时间就忘了。
附上代码:
#include <cstdio> #include <algorithm> #include <cstring> #include <vector> //这道题注意poj提交时要用c++提交!用g++提交会wa //同时要注意结构体的赋值不能直接={......} 会CE using namespace std; const int N = 1e5 + 5; int n, m; vector<double> ys; struct Segment{ double x, y1, y2; int k; }seg[N << 1]; bool cmp(Segment a, Segment b) { return a.x < b.x; } struct node { int l, r; int cnt; double len; }tr[N << 3]; int find(double y) { return lower_bound(ys.begin(), ys.end(), y) - ys.begin(); } void pushup(int u) { //这个pushup一开始我觉得挺难理解的 if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l]; else if (tr[u].l != tr[u].r) { tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len; } else { tr[u].len = 0; } } void build(int u, int l, int r) { tr[u] = {l, r, 0, 0}; //初始化时都是0,所以也不要pushup // tr[u].l = l; // tr[u].r = r; // tr[u].cnt = 0; // tr[u].len = 0; if (l != r) { int mid = l + r >> 1; if (l <= mid) build(u << 1, l, mid); if (r > mid) build(u << 1 | 1, mid + 1, r); } } void modify(int u, int l, int r, int k) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].cnt += k; pushup(u); } else { int mid = tr[u].l + tr[u].r >> 1; if (l <= mid) modify(u << 1, l, r, k); if (r > mid) modify(u << 1 | 1, l, r, k); pushup(u); } } int main() { int cas = 0; while (scanf("%d", &n) != EOF) { if (n == 0) break; double x1, x2, y1, y2; for (int i = 0, j = 0; i < n; ++i) { scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2); seg[j++] = {x1, y1, y2, 1}; // 第一次入边,所以是权值 +1 seg[j++] = {x2, y1, y2, -1}; // 改图形出边,所以权值 -1 // seg[j].x = x1; seg[j].y1 = y1; seg[j].y2 = y2; seg[j].k = 1; // // j++; // seg[j].x = x2; seg[j].y1 = y1; seg[j].y2 = y2; seg[j].k = -1; // j++; ys.push_back(y1); ys.push_back(y2); } sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end()); build(1, 0, ys.size() - 2); // 注意:存的是区间,不是点,所以区间的个数要比点的个数少1 sort(seg, seg + n * 2, cmp); double res = 0; for (int i = 0; i < 2 * n; ++i) { if (i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x); modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k); } printf("Test case #%d\n", ++cas); printf("Total explored area: %.2lf\n\n", res); } }