扫描线

前言

扫描线可以配合线段树解决矩形面积并、周长并的问题。

先上张 OI Wiki 的图:

scanning

这张动图是从下扫到上,本文将以从左扫到右的顺序实现。

扫描线

看完上面那张动图,相信你已经对扫描线有了初步认识。

为了匹配解说,我把图魔改了一下。

现在我们想象一根线从左往右扫过图形,图中它会与所有纵边重合。

我们给它们赋上边权,如图,蓝色边权值为 \(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;
}
posted @ 2024-02-04 21:41  Po7ed  阅读(9)  评论(0编辑  收藏  举报