【学习笔记/模板】扫描线 周长并
周长并
絮絮叨叨,切切喳喳
上午摆,发现自己还有个周长并的坑,打算填,然后一干就一个上午。然而别人一上午五道紫题,人与人的体制不能一概而论了属于是。
晚上先去摆了NOI的笔试模拟,然后就楼了几眼 €€£ 的笔试题库,然后居然神奇的记住了大部分出的题,可能天赋点都点在没啥用(至少对OI用处不大)的地方了。加上今天 rp 爆发rp 都用这上边了,可不你模拟赛场场爆炸呢,不会的还都蒙对了,于是乎可能是唯一一次AK + Rank 1,泵。
例题
P1856 [IOI1998] [USACO5.5] 矩形周长Picture
思想
学周长并的人大抵都学过面积并了罢。
学过面积并肯定对扫描线的思想至少是了解的罢。
和面积并一样,有几个矩形:(图片来自网络)
想象有一条线,从下到上扫描完整个图案,遇到每条上边或下边就停下:
每次停下后再对区间进行处理。
因为是周长,所以还有一条从左扫到右的线。
最后的加和就是周长。
实现
不同于其他大佬令人拍手叫绝的扫一遍操作,本蒟蒻使用的是更好理解的上下,左右各扫一遍。
所以,需要把矩形拆成上下,左右两组边,再用两棵线段树维护。
矩形的边设立一个结构体存储:
struct Line{ int s; //维护线段端点的另一个值 int l, r; //维护线段端点的 x / y 值 int sit; }line_x[MAXN << 1]/*横边*/, line_y[MAXN << 1]/*竖边*/;
为了扫描,要对横边按照 值升序排序,对竖边按照 值升序排序。
需要注意的是,当两个矩形的边重合时,横/竖边的 值相同,这就需要我们让入边在前。
如果不这样的话会多算一遍出边。所以,cmp
函数:
inline bool cmp(const Line &a, const Line &b){ if(a.s == b.s) //如果相同,入边在前 return a.sit > b.sit; return a.s < b.s; //升序排序 }
然后是答案统计,一条边对周长的贡献,在于它对线段树产生的影响,就像人生的意义在于奉献一样。
其实就是和线段树上次存的边相减,取个绝对值。
自己画画图就能出来,才不是因为今天累了,懒得详细写了(英梨梨嘴脸)。
所以,这就没什么了,想清楚还是很好写的。
剩下的维护线段树就和面积并一样了,完结撒花。
Code
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 1e5 + 10; int n, tot_x, tot_y, last_x, last_y; long long sum; int val_x[MAXN], val_y[MAXN]; //存离散化前的值 int x[MAXN], y[MAXN]; //离散化用临时数组 struct Line{ int s; //维护线段端点的另一个值 int l, r; //维护线段端点的 x / y 值 int sit; }line_x[MAXN << 1]/*横边*/, line_y[MAXN << 1]/*竖边*/; inline bool cmp(const Line &a, const Line &b){ if(a.s == b.s) //如果相同,入边在前 return a.sit > b.sit; return a.s < b.s; //升序排序 } struct Segment_Tree{ struct Tree{ int l, r; int len; //区间的线段的长度 int cnt; //有几条覆盖整个区间的线段 }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } inline void Pushup(int rt, int *val){ if(tr[rt].cnt) //如果被整体覆盖了 tr[rt].len = val[tr[rt].r + 1] - val[tr[rt].l]; //就是右端点减去左端点 else //没被整体覆盖 tr[rt].len = tr[lson(rt)].len + tr[rson(rt)].len; //从儿子身上往上捯 } void Build(int rt, int l, int r){ tr[rt].l = l; tr[rt].r = r; tr[rt].len = 0; tr[rt].cnt = 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, int *val){ if(tr[rt].l >= l && tr[rt].r <= r){ //整体覆盖 tr[rt].cnt += data; //其实和懒标记差不多 Pushup(rt, val); return; } int mid = (tr[rt].l + tr[rt].r) >> 1; if(l <= mid) Update(lson(rt), l, r, data, val); if(r > mid) Update(rson(rt), l, r, data, val); Pushup(rt, val); } }S_x/*从下往上扫*/, S_y/*从左往右扫*/; inline int read(){ int 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 << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main(){ n = read(); for(register int i = 1; i <= n; i++){ int x1, y1, x2, y2; x1 = read(), y1 = read(), x2 = read(), y2 = read(); //line_x存横边 line_x[(i << 1) - 1].s = y1, line_x[(i << 1)].s = y2; line_x[(i << 1) - 1].l = line_x[(i << 1)].l = x1; line_x[(i << 1) - 1].r = line_x[(i << 1)].r = x2; line_x[(i << 1) - 1].sit = 1; line_x[(i << 1)].sit = -1; x[++tot_x] = x1; x[++tot_x] = x2; //line_y存竖边 line_y[(i << 1) - 1].s = x1, line_y[(i << 1)].s = x2; line_y[(i << 1) - 1].l = line_y[(i << 1)].l = y1; line_y[(i << 1) - 1].r = line_y[(i << 1)].r = y2; line_y[(i << 1) - 1].sit = 1; line_y[(i << 1)].sit = -1; y[++tot_y] = y1; y[++tot_y] = y2; } n <<= 1; sort(x + 1, x + 1 + tot_x); sort(y + 1, y + 1 + tot_y); int cnt_x = unique(x + 1, x + 1 + tot_x) - x - 1; int cnt_y = unique(y + 1, y + 1 + tot_y) - y - 1; for(register int i = 1; i <= n; i++){ int pos_x1 = lower_bound(x + 1, x + 1 + cnt_x, line_x[i].l) - x; int pos_x2 = lower_bound(x + 1, x + 1 + cnt_x, line_x[i].r) - x; int pos_y1 = lower_bound(y + 1, y + 1 + cnt_y, line_y[i].l) - y; int pos_y2 = lower_bound(y + 1, y + 1 + cnt_y, line_y[i].r) - y; val_x[pos_x1] = line_x[i].l; val_x[pos_x2] = line_x[i].r; line_x[i].l = pos_x1; line_x[i].r = pos_x2; val_y[pos_y1] = line_y[i].l; val_y[pos_y2] = line_y[i].r; line_y[i].l = pos_y1; line_y[i].r = pos_y2; } //离散化基础操作 sort(line_x + 1, line_x + 1 + n, cmp); sort(line_y + 1, line_y + 1 + n, cmp); S_x.Build(1, 1, n); S_y.Build(1, 1, n); for(register int i = 1; i <= n; i++){ S_x.Update(1, line_x[i].l, line_x[i].r - 1, line_x[i].sit, val_x); //更新横边 S_y.Update(1, line_y[i].l, line_y[i].r - 1, line_y[i].sit, val_y); //更新竖边 sum += abs(S_x.tr[1].len - last_x) + abs(S_y.tr[1].len - last_y); //统计答案 last_x = S_x.tr[1].len; //记录上次线段树的值 last_y = S_y.tr[1].len; } printf("%lld\n", sum); return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16584809.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理