扫描线学习笔记

1.引入

扫描线多用于图形上,是一条线在图形上扫来扫去,它一般被用来解决图形面积,周长,以及二维数点等问题。

2.扫描线求面积并

如下图:

我们模拟一条扫描线,使它从下往上扫过整个平面,这条扫描线会在遇到横向线段的时候停下来更新一些东西。那么整个图形就可以找出四条线段,如图:

更新的内容就是计算线段的长度,所以要记录线段左右端点的坐标,我们把坐标存入一个数组,记作x[],并将x排序,如图:

接着考虑,在扫描线单调向上时,如何判断哪里有面积,哪里是空的?

因为一个矩形有上下底,在往上扫的时候,总是先遇到下底,再遇到上底,所以我们可以给每条横向线段赋一个权值,例如下底为1,上底为-1,所以扫描线有权值的部分就是要计算的面积

然后考虑面积并的问题,显而易见,重叠部分只计算一次面积,两个矩形相交,一个矩形的横向边上至少有一个另一个矩形边上的点

如上图,X[1]和X[3]作为左右端点组成的线段可以分成两部分,X[1]和X[2]组成线段和X[2]和X[3]组成线段

所以,这个图形横向分成了4条边3部分,纵向分成了4条边3部分

扫描线从下往上扫,只能处理横向问题,纵向需要用线段树维护

可以用线段树的每一个节点储存一个线段,当然,这条线段不一定是一组左右端点截成的,如图,可以建一棵线段树:

当一条线段被扫到时,立即更新线段树每个节点维护的线段的覆盖长度和权值,例如扫到最下面的线段时,节点1,2维护的线段覆盖长度和权值就会被更新,所以不难看出,线段树维护的左右节点其实是线段的编号,另外维护线段覆盖长度和权值,这样扫描线扫有权值的部分就有我们要计算的面积,那么就更新线段覆盖长度

对于每一条线段,我们记录它的左右端点和纵坐标,权值,按照纵坐标优先升序排序,保证扫描线从下向上扫。

不难看出,x[]数组排序的意义是为了线段树更新覆盖长度时能直接减,另外,相同的X[]我们其实只需要一个就够了,所以我们要离散化处理,所以有:

s=∑线段覆盖长度*扫过的高度(即纵坐标的差)

例题:[poj1151]亚特兰蒂斯

https://gxyzoj.com/d/hzoj/p/3444

#include<cstdio>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,cnt;;
double s[20005];
struct node{
	double x,ay,by;
	int fl;
}p[20005];
bool cmp(node x,node y)
{
	return x.x<y.x;
}
struct seg_tree{
	double sum,l,r;
	int lazy;
}tr[80040];
void pushup(int id)
{
	if(tr[id].lazy>0)
	{
		tr[id].sum=tr[id].r-tr[id].l;
	}
	else
	{
		tr[id].sum=tr[lid].sum+tr[rid].sum;
	}
}
void build(int id,int l,int r)
{
	tr[id].lazy=0;
	tr[id].l=s[l],tr[id].r=s[r];
	if(r-l<=1)
	{
		tr[id].sum=0;
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid,r);
	pushup(id);
}
void update(int id,double ay,double by,int fl)
{
	if(tr[id].l==ay&&tr[id].r==by)
	{
		tr[id].lazy+=fl;
		pushup(id);
		return;
	}
	if(tr[lid].r>ay) update(lid,ay,min(tr[lid].r,by),fl);
	if(tr[rid].l<by) update(rid,max(tr[rid].l,ay),by,fl);
	pushup(id);
}
int main()
{
	scanf("%d",&n);
	while(n!=0)
	{
		for(int i=1;i<=n;i++)
		{
			double ax,ay,bx,by;
			scanf("%lf%lf%lf%lf",&ax,&ay,&bx,&by);
			p[i]=(node){ax,ay,by,1};
			p[i+n]=(node){bx,ay,by,-1};
			s[i]=ay;
			s[i+n]=by;
		}
		sort(s+1,s+2*n+1);
		sort(p+1,p+2*n+1,cmp);
		build(1,1,2*n);
		update(1,p[1].ay,p[1].by,p[1].fl);
		double ans=0;
		for(int i=2;i<=2*n;i++)
		{
			ans+=(p[i].x-p[i-1].x)*tr[1].sum;
		//	printf("%.2lf %.2lf\n",tr[1].sum,ans);
			update(1,p[i].ay,p[i].by,p[i].fl);
		}
		printf("Test case #%d\nTotal explored area: %.2lf\n",++cnt,ans);
		scanf("%d",&n);
	}
	return 0;
}

3.扫描线求周长并

首先考虑纵边,画个图:

标红的是要计算的纵边长度,不难发现,由于两线之间距离固定,所以当前纵边的贡献为和上条线的距离\(\times\)当前边数,当前边数即当前不相交的线段数 ×2(也就是把相交的线段拼在一起后统计),所以,可以用线段树维护当前有多少条线段

记c为当前区间的线段条数,考虑转移,当sum不为0时,其为当前区间长度;否则要从左右儿子转移而来,直接相加肯定不对,因为有一些左右儿子的线段会相交

观察左右儿子的交界处(左儿子右端点、右儿子左端点),当且仅当它们均被线段覆盖时会相交,c需要减 1,所以再维护一个区间左右端点是否被覆盖的lc,rc 即可。sum为0时分别为左儿子lc,右儿子 rc,否则均为 1

接着考虑横边,与求面积并很像,也要枚举区间被覆盖的长度,画张图:

其中,红线为周长的贡献,绿线为区间被覆盖的长度,容易发现,红线就是两两绿线的差

例题: [IOI1998] [USACO5.5] 矩形周长

#include<cstdio>
#include<algorithm>
#include<cmath>
#define lid id<<1
#define rid id<<1|1
#define ll long long
using namespace std;
int n,x[10005];
ll ans;
struct node{
	int ax,bx,y,fl;
}a[10005];
bool cmp(node x,node y)
{
	if(x.y!=y.y) return x.y<y.y;
	return x.fl>y.fl;
}
struct tree{
	int sum,len,c;
	bool lc,rc;
}tr[80040];
void pushup(int id,int l,int r)
{
	if(tr[id].sum)
	{
		tr[id]=(tree){tr[id].sum,x[r+1]-x[l],1,1,1};
	}
	else
	{
		tr[id]=(tree){0,tr[lid].len+tr[rid].len,tr[lid].c+tr[rid].c,tr[lid].lc,tr[rid].rc};
		if(tr[lid].rc&&tr[rid].lc) tr[id].c--;
	}
}
void update(int id,int l,int r,int ql,int qr,int v)
{
	if(ql<=x[l]&&qr>=x[r+1])
	{
		tr[id].sum+=v;
		pushup(id,l,r);
		return;
	}
	int mid=(l+r)>>1;
	if(ql<x[mid+1]) update(lid,l,mid,ql,qr,v);
	if(qr>x[mid+1]) update(rid,mid+1,r,ql,qr,v);
	pushup(id,l,r);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int ax,ay,bx,by;
		scanf("%d%d%d%d",&ax,&ay,&bx,&by);
		a[i]=(node){ax,bx,ay,1};
		a[i+n]=(node){ax,bx,by,-1};
		x[i]=ax,x[i+n]=bx;
	}
	sort(a+1,a+2*n+1,cmp);
	sort(x+1,x+2*n+1);
	int m=unique(x+1,x+n*2+1)-x-1;
	int lst=0;
	//	printf("1");
	for(int i=1;i<2*n;i++)
	{
		update(1,1,m-1,a[i].ax,a[i].bx,a[i].fl);
		ans=ans+1ll*2*tr[1].c*(a[i+1].y-a[i].y)+1ll*abs(tr[1].len-lst);
		lst=tr[1].len;
	}
	printf("%lld",ans+a[n*2].bx-a[n*2].ax);
	return 0;
}
posted @ 2024-02-23 12:20  wangsiqi2010916  阅读(7)  评论(0编辑  收藏  举报