【学习笔记/模板】扫描线 面积并
例题:P5490 【模板】扫描线
update at 2022.5.28 更新到了当前版本(雾)的码风
因为切不动线段树合并就去搞扫描线了
虽然扫描线也搞了一晚上加半个上午
切入正题
扫描线是一种为了处理如多个矩形有重叠的覆盖在一起的不规则图形的算法。
如图:(图片来自网络)
整个图形的面积就转化成了各个矩形的面积。
需要做的就是用线段树维护这样一条扫描线。
如图:(图片还是来自网络)(我不会画)
-
从左往右扫,记录每一条竖直方向上的线段(x表示该线段的横坐标;yl表示该线段下顶点的纵坐标;yh表示该线段上顶点的纵坐标;k为加减的标记(一个矩形先被扫到的线段即左边界为+1、后被扫到即右边界为-1))。
-
之后,就可以进行离散化了,毕竟1e9的区间长度开线段树不现实。需要离散化的就是每条线段的y坐标上界yh和y坐标下界yl,先把它们的值存在一个临时数组里,再把yl和yh的值都扔进一个val数组,最后把yl和yh的值离散成它们的值在val数组里的下标。
离散化部分:
sort(y + 1, y + n + 1); cnt = unique(y + 1, y + n + 1) - y - 1;//离散化基本操作 for(register int i = 1; i <= n; i++){ int pos1 = lower_bound(y + 1, y + cnt + 1, len[i].yl) - y; //查找yl的位置 int pos2 = lower_bound(y + 1, y + cnt + 1, len[i].yh) - y; //查找yh的位置 val[pos1] = len[i].yl; val[pos2] = len[i].yh; //把它们的值扔进val数组里 len[i].yl = pos1; len[i].yh = pos2; //离散成对应坐标 }
-
在经过离散化之后,线段树维护的区间端点l,r的含义也发生了变化,变成了区间(,)。
-
再往后,需要对这些线段进行排序,肯定要让x值小的先被扫到。
这样,就形成了上图。
那还有问题吗?显然有啊(废话)
如果,线段树中的时候,代表区间,变成了一个点。
不妨假设总区间为,有一条扫描线,。
加入线段树,修改值。
修改长度为。
修改长度为。
欸嘿?应该被修改为才对。
仿佛需要改变线段树储存的值的意义。
用表示左区间,值为;
用表示右区间,值为;
再看之前的例子,,。
修改长度为。
这样就没问题了。
最后摘抄一段蓝皮书上的话:
对于每一个,我们再上执行期间修改。该区间被线段树划分成 log n个节点,我们把这些节点的都加。
对于线段树中任意一个节点,若,则等于,否则,该点等于两个子节点的之和。在一个节点的被修改,以及线段树从下往上传递信息时,我们都按照该方法更新值。根节点的值就是整个扫描线上被覆盖的长度
Code
//从左向右扫 #include<cstdio> #include<algorithm> using namespace std; const int MAX = 1e6+10; int n, tot, cnt; long long ans; long long val[MAX << 1], y[MAX << 1]; struct Tree{ int l, r;//左右区间 int cnt;//该节点自身被覆盖的次数 int len;//该节点代表的区间被矩形覆盖的长度 }bt[MAX << 2]; struct Line{ long long x;//竖线的横坐标 long long yh, yl;//yh:竖线的上端点的纵坐标; yl:竖线的下端点的纵坐标 long long sit;//记录该竖线是矩形的左边界还是右边界 }len[MAX << 1];//记录每条竖线 inline long long read(){ long long x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); } return x * f; } inline bool cmp(const Line &a, const Line &b){ return a.x < b.x; }//依据每条线段x坐标的大小排序 inline int lson(int x){ return x << 1; } inline int rson(int x){ return x << 1 | 1; } inline void pushup(int rt){ if(bt[rt].cnt) bt[rt].len = val[bt[rt].r + 1] - val[bt[rt].l]; else bt[rt].len = bt[lson(rt)].len + bt[rson(rt)].len; }//向上更新节点 void build(int rt, int l, int r){ bt[rt].l = l; bt[rt].r = r; bt[rt].cnt = 0; bt[rt].len = 0; //最初,被覆盖的次数和长度都为0 if(l == r) return;//叶子节点没有意义,直接退出 int mid = (l + r) >> 1; build(lson(rt), l, mid); build(rson(rt), mid + 1, r); } void update(int rt, int l, int r, int data){ if(bt[rt].l >= l && bt[rt].r <= r){ bt[rt].cnt += data; pushup(rt);//注意在这里也要更新 return; } int mid = (bt[rt].l + bt[rt].r) >> 1; if(l <= mid) update(lson(rt), l, r, data); if(r > mid) update(rson(rt), l, r, data); pushup(rt); } int main(){ n = read(); for(register int i = 1; i <= n; i++){ long long x1, y1, x2, y2; x1 = read(), y1 = read(), x2 = read(), y2 = read(); len[(i << 1) - 1].x = x1, len[i << 1].x = x2; len[(i << 1) - 1].yl = len[i << 1].yl = y1; len[(i << 1) - 1].yh = len[i << 1].yh = y2; len[(i << 1) - 1].sit = 1/*左边界*/, len[i << 1].sit = -1;/*右边界*/ y[++tot] = y1; y[++tot] = y2;//存入临时数组,准备离散化 } n <<= 1;//n*2方便下边操作 sort(y + 1, y + n + 1); cnt = unique(y + 1, y + n + 1) - y - 1;//去重 for(register int i = 1; i <= n; i++){ int pos1 = lower_bound(y + 1, y + cnt + 1, len[i].yl) - y;//查找yl的位置 int pos2 = lower_bound(y + 1, y + cnt + 1, len[i].yh) - y;//查找yh的位置 val[pos1] = len[i].yl; val[pos2] = len[i].yh;//把它们的值扔进val数组里 len[i].yl = pos1; len[i].yh = pos2;//离散成对应坐标 } sort(len + 1, len + n + 1, cmp);//按x坐标升序排序 build(1, 1, n); for(register int i = 1; i < n/*最后一条线段之后肯定不跟着另一条线段,它肯定对最后结果没有贡献*/; i++){ update(1, len[i].yl, len[i].yh - 1, len[i].sit);//区间加 ans += bt[1].len * (len[i + 1].x - len[i].x);//根节点的len值*与下一条线段的距离=这一块内的矩形面积 } printf("%lld", ans); return 0; }
//换个方法,从上往下扫 #include<cstdio> #include<algorithm> using namespace std; const int MAX = 1e6+10; int n, tot, cnt; long long ans; long long val[MAX << 1], x[MAX << 1]; struct Tree{ int l, r; int cnt; int len; }bt[MAX << 2]; struct Line{ long long y; long long l, r; long long sit; }len[MAX << 1]; inline long long read(){ long long x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); } return x * f; } inline bool cmp(const Line &a, const Line &b){ return a.y > b.y; } inline int lson(int x){ return x << 1; } inline int rson(int x){ return x << 1 | 1; } inline void pushup(int rt){ if(bt[rt].cnt) bt[rt].len = val[bt[rt].r + 1] - val[bt[rt].l]; else bt[rt].len = bt[lson(rt)].len + bt[rson(rt)].len; } void build(int rt, int l, int r){ bt[rt].l = l; bt[rt].r = r; bt[rt].cnt = 0; bt[rt].len = 0; if(l == r) return; int mid = (l + r) >> 1; build(lson(rt), l, mid); build(rson(rt), mid + 1, r); } void update(int rt, int l, int r, int data){ if(bt[rt].l >= l && bt[rt].r <= r){ bt[rt].cnt += data; pushup(rt); return; } int mid = (bt[rt].l + bt[rt].r) >> 1; if(l <= mid) update(lson(rt), l, r, data); if(r > mid) update(rson(rt), l, r, data); pushup(rt); } int main(){ n = read(); for(register int i = 1; i <= n; i++){ long long x1, y1, x2, y2; x1 = read(), y1 = read(), x2 = read(), y2 = read(); len[(i << 1) - 1].y = y1, len[i << 1].y = y2; len[(i << 1) - 1].l = len[i << 1].l = x1; len[(i << 1) - 1].r = len[i << 1].r = x2; len[(i << 1) - 1].sit = -1, len[i << 1].sit = 1; x[++tot] = x1; x[++tot] = x2; } n <<= 1; sort(x + 1, x + n + 1); cnt = unique(x + 1, x + n + 1) - x - 1; for(register int i = 1; i <= n; i++){ int pos1 = lower_bound(x + 1, x + cnt + 1, len[i].l) - x; int pos2 = lower_bound(x + 1, x + cnt + 1, len[i].r) - x; val[pos1] = len[i].l; val[pos2] = len[i].r; len[i].l = pos1; len[i].r = pos2; } sort(len + 1, len + n + 1, cmp); build(1, 1, n); for(register int i = 1; i < n; i++){ update(1, len[i].l, len[i].r - 1, len[i].sit); ans += bt[1].len * (len[i].y - len[i + 1].y); } printf("%lld", ans); return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16496494.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理