扫描线
前言
扫描线可以配合线段树解决矩形面积并、周长并的问题。
先上张 OI Wiki 的图:
这张动图是从下扫到上,本文将以从左扫到右的顺序实现。
扫描线
看完上面那张动图,相信你已经对扫描线有了初步认识。
为了匹配解说,我把图魔改了一下。
现在我们想象一根线从左往右扫过图形,图中它会与所有纵边重合。
我们给它们赋上边权,如图,蓝色边权值为 \(1\),红色为 \(-1\):
显然每个矩形中扫描线先扫到的纵边就是蓝边,否则为红边。
紧接着像图中那样,全部纵坐标在 \(y\) 轴上截出 \(5\) 条线段。
我们把对这 \(5\) 条线段建立线段树,当扫描线扫过一条纵边时,就将纵边所对应的所有 \(y\) 轴上的线段进行区间修改(\(\pm 1\))。这个过程可以看文首的动图,一眼就懂,清晰明了。注意 \(x\) 轴下方的数字!
每次扫描线碰到纵边,就进行区间修改。然后我们查询线段树中当前的总长(紫色) \(\times\) 当前纵边和下一条纵边的距离(蓝色),再累加,就得到了总面积。如图:
Q:注意到动图中有些线段的 \(cnt\) 算了多次,怎么去重?
A:通过线段树特殊的合并方式。具体可以看代码。
代码
#include <iostream> #include <vector> #include <algorithm> typedef long long ll; constexpr int N=114514; int n,m; struct seg{int x,y1,y2,v;};// 存纵边的 struct seg s[N<<1]; int y[N<<1];// 存所有出现过的 y 值 namespace sgt// 建议先看 main() { struct node{int sum,len;node():sum(0),len(0){}};// 线段树的节点 // ^~~~~~~~~~~~~~~~~~~~~~ 这是默认构造函数(默认初始化值) // 注意每个节点表示一条线段 // 保存两个值:sum(即动图中的 cnt,但对于非叶子节点,sum 表示一整条线段而非一部分的累加次数) // len:当前线段中被取了的部分的长度 // 以上两个变量不理解可以结合下面的函数理解 node t[N<<4]; #define ls (pos<<1) #define rs (pos<<1|1) #define mid ((l+r)>>1) // 以下 pos 为线段树内节点编号,l 为线段树内左端点,r 右端点 inline void update(int pos=1,int l=1,int r=m-1)// 注意这里是 m-1,因为 m 个不同的 y 值只截出了 m-1 条线段(可以结合图理解) {// 特殊的合并方式 if(t[pos].sum)t[pos].len=y[r+1]-y[l];// 如果这一整条线段被取过,那么被取过的长度即为整条线段的长度 else t[pos].len=t[ls].len+t[rs].len;// 否则需要合并左右儿子的长度 } // 新增线段的位置;类型(1/-1) void modify(int yl,int yr,int v,int pos=1,int l=1,int r=m-1) { if(yl<=y[l]&&y[r+1]<=yr)return t[pos].sum+=v,update(pos,l,r);// 如果完全包含整条线段,直接加出现次数,并更新 if(yl<y[mid+1])modify(yl,yr,v,ls,l,mid);// 如果修改区间涵盖了左儿子,修改 if(y[mid+1]<yr)modify(yl,yr,v,rs,mid+1,r);// 同理 update(pos,l,r);// 记得更新 } } // namespace sgt int main() { scanf("%d",&n); int x1,y1,x2,y2; for(int i=1;i<=n;i++)// 输入每个矩形 { scanf("%d %d %d %d",&x1,&y1,&x2,&y2); s[i]={x1,y1,y2,1};// 保存两条纵边 s[i+n]={x2,y1,y2,-1}; y[i]=y1,y[i+n]=y2;// 保存所有出现过的 y 值 } n<<=1;// 方便处理 std::sort(s+1,s+n+1,[](seg a,seg b)->bool {return a.x<b.x;});// 对所有纵边按 x 坐标排序(从左到右) std::sort(y+1,y+n+1);// 离散化 m=std::unique(y+1,y+n+1)-y-1;// 共 m 个不同的 y ll ans=0; for(int i=1;i<n;i++)// 从左到右扫过每条纵边 { sgt::modify(s[i].y1,s[i].y2,s[i].v);// 标记出现 ans+=1ll*sgt::t[1].len*(s[i+1].x-s[i].x);// 计算面积,累加 } printf("%lld",ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具