扫描线-学习笔记

扫描线-学习笔记

引言:扫描线算法用于解决给出多个矩形组成的图形求解其面积、周长等问题。时间复杂度常见为 \(O(n\log_2^n)\) 级别,空间复杂度略大于 \(O(n)\) ,属于线段树的一种运用。

一、求面积

题目:P5490【模板】扫描线 & 矩形面积并

\(n\) 个四边平行于坐标轴的矩形的面积并。输入文件的第一行是一个整数 \(n\),然后输入 \(n\) 行,\(x1,y1,x2,y2\) ,表示一个矩形的四个端点坐标为 \((x1,y1),(x1,y2),(x2,y2),(x2,y1)\)

扫描线跟其名称一样,是一条线在坐标轴上自下而上扫过这个图形(也可以左右扫),每次经过的区域需要保持截面相同,见下图:

我们发现对于这个不规则图形其总面积为每次 \(经过高度\times相截长度\) 的总和,相当于把这个不规则图形的面积分成了数个规则矩形的面积和。

我们对于每个输入的矩形存储其的上下两条边,底边贡献为一,顶边贡献为负一(相当于加边和删边)。对所有的边按照纵坐标进行排序就是这条线需要经过的地方。

需要使用线段树维护相截长度,线段树所表示的区间是离散化过后的。对于一个节点维护两个值,分别是 \(Laz\)
\(sum\) ,表示这个区间被完全覆盖的次数和这个区间被覆盖的大小。

特别的,如果这个节点 \(Laz>0\) 那么其 \(sum\) 为区间大小。
\(Laz\) 不用向下传递。

代码如下:( \(X[i]\) 表示离散化后的 \(i\) 对应离散前的数值)

#include<bits/stdc  .h>
#define int long long
using namespace std;
int read(){
	int x=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10 (ch-'0');
		ch=getchar();
	}
	return x*w;
}
struct lin{
	int l,r,h,mark; //边
}line[4000032];
bool cmp(const lin &x,const lin &y){
	return x.h <y.h ;
}
int n,X[4000032];
struct node{
	int Laz,sum,sl,sr;
}tree[4000032];
void build(int p,int l,int r){
	tree[p].sl=l;
	tree[p].sr=r;
	tree[p].sum=tree[p].Laz=0;
	if(l==r) return;
	int mid=(l r)>>1;
	build(p*2,l,mid);
	build(p*2 1,mid 1,r);
}
void push_up(int p){
	if(tree[p].Laz) tree[p].sum=X[tree[p].sr 1]-X[tree[p].sl];
	else tree[p].sum=tree[p*2].sum tree[p*2 1].sum;
}
void add(int p,int L,int R,int w){
	if(X[tree[p].sr 1]<=L||R<=X[tree[p].sl]) return;
	if(L<=X[tree[p].sl]&&X[tree[p].sr 1]<=R){
		tree[p].Laz =w;
		push_up(p);
		return;
	}
	int mid=(tree[p].sl tree[p].sr)>>1;
	add(p*2,L,R,w);
	add(p*2 1,L,R,w);
	push_up(p);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i  ){
		int x1,x2,y1,y2;
		x1=read();y1=read();x2=read();y2=read();
		X[i*2-1]=x1;
		X[i*2]=x2;
		line[i*2-1]=(lin){x1,x2,y1,1}; //存边
		line[i*2]=(lin){x1,x2,y2,-1};
	}
	n<<=1;
	sort(line 1,line n 1,cmp);
	sort(X 1,X n 1); //离散化
	int tot=unique(X 1,X n 1)-X-1; //求离散化后的数组大小
	build(1,1,tot-1);
	int ans=0;
	for(int i=1;i<n;i  ){
		add(1,line[i].l,line[i].r,line[i].mark);
		ans =tree[1].sum*(line[i 1].h-line[i].h);
	}
	cout<<ans<<endl;
	return 0;
}

例题:P10096 扫地机器人

二、求周长

题目:P1856 矩形周长Picture

墙上贴着许多形状相同的照片。它们的边都是水平和垂直的。每个矩形图片可能部分或全部的覆盖了其他图片。所有矩形合并后的边长称为周长。输入文件的第一行是一个整数 \(N\) ,表示有多少个矩形。接下来 \(N\) 行给出了每一个矩形左下角坐标和右上角坐标。

求周长可以分成两个部分求解,纵边和横边。

总周长为纵边长和横边长之和。

纵边长:

对于每次操作,纵边长为 \(线与图形相交不相连线段数\times高度差\times2\) 。如图,线1与图形相交不相连线段为 1 和 2,共两条。线2与图形相交不相连线段为 3 ,共一条。其实 \(线与图形相交不相连线段数\times2\) 就是纵边数量。

如何维护?

对于线段树上一个节点,再维护三个值,分别是 \((bool)lc\)\((bool)rc\)、c 其中\(lc\)\(rc\) 分别表示是否覆盖到区间左端、是否覆盖到区间右端, \(c\) 表示区间内线与图形相交不相连线段数。得到以下公式:

\(tree[p].c= \left\{ \begin{array}{lc} tree[lson].c+tree[rson].c-1& tree[lson].rc=1且tree[rson].lc=1 \\ tree[lson].c+tree[rson].c&other\\ \end{array} \right.\)

形象化的:

维护即可。

横边长:

对于每次操作,横边长为 \(\left| (上次相截长度-本次相截长度) \right|\) ,维护相截长度和上文相同方法。

注意:

对于下图这种情况(边的高度相同)要先遍历底边再遍历顶边。

图5)

正确顺序的 \(ans\)\(tree[1].sum\) 变化

顺序 tree[1].sum ans
1 4 12
3 4 12
2 4 20
4 0 24

错误顺序的 \(ans\)\(tree[1].sum\) 变化

顺序 tree[1].sum ans
1 4 12
2 0 16
3 4 28
4 0 32

原因:删边之后此位置还有边,但错误顺序以为此处无边。

代码如下:

#include<bits/stdc  .h>
#define int long long
using namespace std;
int read(){
	int x=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10 (ch-'0');
		ch=getchar();
	}
	return x*w;
}
struct lin{
	int l,r,h,mark;
}line[4000032];
bool cmp(const lin &x,const lin &y){
	if(x.h==y.h) return x.mark>y.mark;
	return x.h <y.h ;
}
int n,X[4000032];
struct node{
	int Laz,sum,sl,sr,c,lc,rc;
}tree[4000032];
void build(int p,int l,int r){
	tree[p].sl=l;
	tree[p].sr=r;
	tree[p].sum=tree[p].Laz=0;
	if(l==r) return;
	int mid=(l r)>>1;
	build(p*2,l,mid);
	build(p*2 1,mid 1,r);
}
void push_up(int p){
	if(tree[p].Laz){
		tree[p].sum=X[tree[p].sr 1]-X[tree[p].sl];
		tree[p].c=1;
		tree[p].lc=tree[p].rc=1;
	}else{
		tree[p].sum=tree[p*2].sum tree[p*2 1].sum;
		tree[p].c=tree[p*2].c tree[p*2 1].c;
		if(tree[p*2].rc&&tree[p*2 1].lc) tree[p].c-=1;
		tree[p].lc=tree[p*2].lc;
		tree[p].rc=tree[p*2 1].rc;
	}
}
void add(int p,int L,int R,int w){
	if(X[tree[p].sr 1]<=L||R<=X[tree[p].sl]) return;
	if(L<=X[tree[p].sl]&&X[tree[p].sr 1]<=R){
		tree[p].Laz =w;
		push_up(p);
		return;
	}
	add(p*2,L,R,w);
	add(p*2 1,L,R,w);
	push_up(p);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i  ){
		int x1,x2,y1,y2;
		x1=read();y1=read();x2=read();y2=read();
		X[i*2-1]=x1;
		X[i*2]=x2;
		line[i*2-1]=(lin){x1,x2,y1,1};
		line[i*2]=(lin){x1,x2,y2,-1};
	}
	n<<=1;
	sort(line 1,line n 1,cmp);
	sort(X 1,X n 1); //离散化
	int tot=unique(X 1,X n 1)-X-1; //求离散化后的数组大小
	build(1,1,tot-1);
	int ans=0,last=0;
	for(int i=1;i<n;i  ){
		add(1,line[i].l,line[i].r,line[i].mark);
		ans =abs(tree[1].sum-last);
		ans =2*tree[1].c*(line[i 1].h-line[i].h);
		last=tree[1].sum;
	}
	ans =line[n].r-line[n].l;
	cout<<ans<<endl;
	return 0;
}

\(------------------------------------\)

再附带上面求面积的例题代码:

#include<bits/stdc  .h>
#define int long long
using namespace std;
int read(){
	int x=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10 (ch-'0');
		ch=getchar();
	}
	return x*w;
}

struct lin{
	int l,r,h,mark;
}line[4000032];
bool cmp(const lin &x,const lin &y){
	return x.h <y.h ;
}
int n,X[4000032];
struct node{
	int Laz,sum,sl,sr;
}tree[4000032];
void build(int p,int l,int r){
	tree[p].sl=l;
	tree[p].sr=r;
	tree[p].sum=tree[p].Laz=0;
	if(l==r) return;
	int mid=(l r)>>1;
	build(p*2,l,mid);
	build(p*2 1,mid 1,r);
}
void push_up(int p){
	if(tree[p].Laz) tree[p].sum=X[tree[p].sr 1]-X[tree[p].sl];
	else tree[p].sum=tree[p*2].sum tree[p*2 1].sum;
}
void add(int p,int L,int R,int w){
	if(X[tree[p].sr 1]<=L||R<=X[tree[p].sl]) return;
	if(L<=X[tree[p].sl]&&X[tree[p].sr 1]<=R){
		tree[p].Laz =w;
		push_up(p);
		return;
	}
	int mid=(tree[p].sl tree[p].sr)>>1;
	add(p*2,L,R,w);
	add(p*2 1,L,R,w);
	push_up(p);
}
signed main(){
	int k=read(),nx=0,ny=0;
	n=read();
	for(int i=1;i<=n;i  ){
		char opp[3];
		scanf("%s",opp);char op=opp[0];
		int x=read();
		if(op=='N'){
			X[i*2-1]=nx;
			X[i*2]=nx k;
			line[i*2-1]=(lin){nx,nx k,ny,1};
			line[i*2]=(lin){nx,nx k,ny x k,-1};
			ny =x;
		}else if(op=='E'){
			X[i*2-1]=nx;
			X[i*2]=nx x k;
			line[i*2-1]=(lin){nx,nx x k,ny,1};
			line[i*2]=(lin){nx,nx x k,ny k,-1};
			nx =x;
		}else if(op=='W'){
			X[i*2-1]=nx k;
			X[i*2]=nx-x;
			line[i*2-1]=(lin){nx-x,nx k,ny,1};
			line[i*2]=(lin){nx-x,nx k,ny k,-1};
			nx-=x;
		}else{
			X[i*2-1]=nx;
			X[i*2]=nx k;
			line[i*2-1]=(lin){nx,nx k,ny-x,1};
			line[i*2]=(lin){nx,nx k,ny k,-1};
			ny-=x;
		}
	}
	n<<=1;
	sort(line 1,line n 1,cmp);
	sort(X 1,X n 1);
	int tot=unique(X 1,X n 1)-X-1;
	build(1,1,tot-1);
	int ans=0;
	for(int i=1;i<n;i  ){
		add(1,line[i].l,line[i].r,line[i].mark);
		ans =tree[1].sum*(line[i 1].h-line[i].h);
	}
	cout<<ans;
	return 0;
}

感谢阅读orz

posted @   WoXitao  阅读(85)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示