https://ncc79601.blog.luogu.org/scan-line
题目链接:https://vjudge.net/problem/POJ-1151
有时候一篇好的题解真的救人命,扫描线的原理一看就会,但代码实现着实费了老大功夫。上上下下找了不少题解,要么晦涩难懂,要么码风实在劝退。
- 算法描述:
扫描线通常用来求叠加矩形的总面积,顾名思义,即对复合后的复杂图形从下至上或从左至右进行扫描,将图形拆分成多个小块,方便求取。小块的面积则是相邻两条扫描线的距离差乘以小块的另一条边长,而线段树则用来维护扫描线截取的线段长度。详情不再赘述,如果还有疑惑,请参照上述题解。#include <stdio.h> #include <iostream> #include <algorithm> #define lson (x << 1) #define rson (x << 1 | 1) using namespace std; const int MAXN = 1e6 + 10; typedef long long ll; int n, cnt = 0; ll x1, y1, x2, y2, X[MAXN << 1]; struct ScanLine { ll l, r, h; int mark; // mark用于保存权值 (1 / -1) bool operator < (const ScanLine &rhs) const { return h < rhs.h; } } line[MAXN << 1]; struct SegTree { int l, r, sum; ll len; // sum: 被完全覆盖的次数; // len: 区间内被截的长度。 } tree[MAXN << 2]; void build_tree(int x, int l, int r) { // 我觉得最不容易写错的一种建树方法 tree[x].l = l, tree[x].r = r; tree[x].len = 0; tree[x].sum = 0; if(l == r) return; int mid = (l + r) >> 1; build_tree(lson, l, mid); build_tree(rson, mid + 1, r); return; } void pushup(int x) { int l = tree[x].l, r = tree[x].r; if(tree[x].sum /* 也就是说被覆盖过 */ ) tree[x].len = X[r + 1] - X[l]; // 更新长度 else tree[x].len = tree[lson].len + tree[rson].len; // 合并儿子信息 } void edit_tree(int x, ll L, ll R, int c) { int l = tree[x].l, r = tree[x].r; // 注意,l、r和L、R的意义完全不同 // l、r表示这个节点管辖的下标范围 // 而L、R则表示需要修改的真实区间 if(X[r + 1] <= L || R <= X[l]) return; // 这里加等号的原因: // 假设现在考虑 [2,5], [5,8] 两条线段,要修改 [1,5] 区间的sum // 很明显,虽然5在这个区间内,[5,8] 却并不是我们希望修改的线段 // 所以总结一下,就加上了等号 if(L <= X[l] && X[r + 1] <= R) { tree[x].sum += c; pushup(x); return; } edit_tree(lson, L, R, c); edit_tree(rson, L, R, c); pushup(x); } int main() { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%lld %lld %lld %lld", &x1, &y1, &x2, &y2); X[2 * i - 1] = x1, X[2 * i] = x2; line[2 * i - 1] = (ScanLine) {x1, x2, y1, 1}; line[2 * i] = (ScanLine) {x1, x2, y2, -1}; // 一条线段含两个端点,一个矩形的上下边都需要扫描线扫过 } n <<= 1; // 直接把 n <<= 1 方便操作 sort(line + 1, line + n + 1); sort(X + 1, X + n + 1); int tot = unique(X + 1, X + n + 1) - X - 1; // 去重最简单的方法:使用unique!(在<algorithm>库中) build_tree(1, 1, tot - 1); // 为什么是 tot - 1 : // 因为右端点的对应关系已经被篡改了嘛… // [1, tot - 1]描述的就是[X[1], X[tot]] ll ans = 0; for(int i = 1; i < n /* 最后一条边是不用管的 */ ; i++) { edit_tree(1, line[i].l, line[i].r, line[i].mark); // 先把扫描线信息导入线段树 ans += tree[1].len * (line[i + 1].h - line[i].h); // 然后统计面积 } printf("%lli", ans); return 0; }
下面同样给出一道扫描线+线段树的例题,思路与上题如出一辙,个人觉得对星星的处理比较巧妙,本来是用方框去框住更多的星星,现在根据星星的坐标确定方框右端点所能存在的范围,题目一下子就从抽象变为具体了,妙!
题目链接:https://vjudge.net/problem/POJ-2482#author=PMYCQACF#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int N = 10006; int n; struct P { unsigned int x, y, z; int c; bool operator < (const P w) const { return x < w.x || (x == w.x && c < w.c); } } a[N<<1]; unsigned int w, h, b[N<<1]; struct T {//ans保存最大值 int l, r, ans, add; } t[N<<3]; void build(int p, int l, int r) { t[p].l = l; t[p].r = r; t[p].ans = t[p].add = 0; if (l == r) return; int mid = (l + r) >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); } void spread(int p) { t[p<<1].add += t[p].add; t[p<<1].ans += t[p].add; t[p<<1|1].add += t[p].add; t[p<<1|1].ans += t[p].add; t[p].add = 0; } void change(int p, int l, int r, int c) { if (l <= t[p].l && r >= t[p].r) { t[p].add += c; t[p].ans += c; return; } if (t[p].add) spread(p); int mid = (t[p].l + t[p].r) >> 1; if (l <= mid) change(p<<1, l, r, c); if (r > mid) change(p<<1|1, l, r, c); t[p].ans = max(t[p<<1].ans, t[p<<1|1].ans); } void Stars_in_Your_Window() { for (int i = 1; i <= n; i++) { int k = i << 1; scanf("%u %u %d", &a[k-1].x, &a[k-1].y, &a[k-1].c); a[k].x = a[k-1].x + w;//建立如(x,y,y+h-1,c) and (x+w,y,y+h-1,c) 的四元组,之所以-1是为了应对星星在边界的情况 b[k-1] = a[k].y = a[k-1].y; b[k] = a[k].z = a[k-1].z = a[k].y + h - 1; a[k].c = -a[k-1].c; } n <<= 1; sort(b + 1, b + n + 1); int m = unique(b + 1, b + n + 1) - (b + 1); for (int i = 1; i <= n; i++) { a[i].y = lower_bound(b + 1, b + m + 1, a[i].y) - b;//离散化的处理 a[i].z = lower_bound(b + 1, b + m + 1, a[i].z) - b; } sort(a + 1, a + n + 1); build(1, 1, m); int ans = 0; for (int i = 1; i <= n; i++) { change(1, a[i].y, a[i].z, a[i].c); ans = max(ans, t[1].ans); } cout << ans << endl; } int main() { while (cin >> n >> w >> h) Stars_in_Your_Window(); return 0; }