扫描线(模板向)

关于计算几何中的扫描线
推荐这篇大佬的博客这篇
此篇博客仅作为个人的整理

扫描线一般适用于求几个矩形(有重叠)面积和或者周长和
我们的办法类似这张图
在这里插入图片描述




PS:

  1. 空间要开的足够大,我们这里的线段树跟普通线段是有差异,空间最好开8倍
  2. 变量名y1如果用,会有问题,最好办法:不用


求面积和模板(带离散化的版本):

//本模板是从坐往右扫的,从下往上扫同理
#define ls (rt<<1)
#define rs (rt<<1|1)


LL cover[N*8];//存放i节点对应区间覆盖情况的值
LL n;
LL len[N*8];
LL yy[N*2];//存放离散后的y值,下标用lowerbound进行查找
LL cnt = 0;
struct node
{
	LL x;
	LL up,down;;//边的y坐标上,y坐标下
	LL inout;//入边为1,出边为-1
}line[N*2],u;

bool cmp(node a,node b)
{
	if( a.x==b.x ) return a.inout>b.inout;//一定是加边在前面!!! 
	return a.x < b.x;
}

void pushup( LL l,LL r,LL rt )//pushup其实主要就思考在什么情况,需要更新哪些信息来维护线段树
{
	if( cover[rt]>0 ) len[rt] = yy[r] - yy[l];//如果某个节点的cover为正,那么这个点对应区间的长度全为有效的
	else if( l+1==r ) len[rt] = 0;//到了叶子节点
	else len[rt] = len[ls] + len[rs];
}

void update( LL yl,LL yr,LL pd,LL l,LL r,LL rt )
{
	if( yl>r || yr<l ) return ;
	if( yl<=l && yr>=r )
	{
		cover[rt] += pd;//根据出边入边,加上相应的值
		pushup( l,r,rt );
		return ;
	}
	if( l+1==r ) return ;//到子节点,这句一定要有!!!
	LL mid = (l+r)>>1;
	if( yl<=mid )
	{
		update( yl,yr,pd,l,mid,ls );
	}
	if( yr>mid )
	{
		update( yl,yr,pd,mid,r,rs );//这里不再是m+1,因为要进入类似[1,2][2,3]的叶子节点
	}
	
	pushup( l,r,rt );
}

void solve()
{
	cnt = 0;
	scanf("%lld",&n);
	for(LL i=1;i<=n;i++)
	{
		LL x1,x2,y11,y2;
		scanf("%lld %lld %lld %lld",&x1,&y11,&x2,&y2);
		u.x = x1;u.down = y11,u.up = y2,u.inout = 1;
		line[ ++cnt ] = u;//给入边赋值
		yy[cnt] = y11;//获得y值
		
		u.x = x2;u.down = y11,u.up = y2,u.inout = -1;
		line[ ++cnt ] = u;
		yy[cnt] = y2;
		
	}
	sort( yy+1,yy+cnt+1 );//给yy排个序
	sort( line+1,line+cnt+1,cmp );//给line按照x轴方向从左到右排序
	LL length = unique( yy+1,yy+cnt+1 ) - (yy+1);//进行离散化操作,unique返回重复位置指针,减去(头指针+1)是数组开始的地方得到数组长度
	memset( cover,0,sizeof( cover ) );
	memset( len,0,sizeof(len) );
	LL ans = 0;
	for(LL i=1;i<=cnt;i++)
	{
		ans += len[1] * ( line[i].x - line[i-1].x );
		LL yl = lower_bound( yy+1,yy+length+1,line[i].down ) - yy;
		LL yr = lower_bound( yy+1,yy+length+1,line[i].up ) - yy;
		LL pd = line[i].inout;
		update( yl,yr,pd,1,length,1 );
	}
	printf("%lld\n",ans);
}






一道模板题

题目描述
求 n 个矩形的面积并。

输入格式
第一行一个正整数 n。

接下来 nn 行每行四个非负整数 x 1 , y 1 , x 2 , y 2 x_1, y_1, x_2, y_2 x1,y1,x2,y2,表示一个矩形的左下角坐标为 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) ,右上角坐标为 ( x 2 , y 2 ) (x_2, y_2) (x2,y2)

输出格式
一行一个正整数,表示 n n n 个矩形的并集覆盖的总面积。

ACcode:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
//#include<cmath>
#define LL long long
#define N 100005
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;

LL cover[N*10],n;
LL len[N*10],yy[N*2],cnt = 0;
struct node
{
	LL x,upy,downy,inout;
}line[N*2],u;

bool cmp(node a,node b)
{
	return a.x < b.x;
}

void pushup( LL l,LL r,LL rt )
{
	if( cover[rt]>0 ) len[rt] = yy[r] - yy[l];
	else if( l+1==r ) len[rt] = 0;
	else len[rt] = len[ls] + len[rs];
}

void update( LL yl,LL yr,LL pd,LL l,LL r,LL rt )
{
	if( yl>r || yr<l ) return ;
	if( yl<=l && yr>=r )
	{
		cover[rt] += pd;
		pushup( l,r,rt );
		return ;
	}
	if( l+1==r ) return ;
	LL mid = (l+r)>>1;
	if( yl<=mid )
	{
		update( yl,yr,pd,l,mid,ls );
	}
	if( yr>mid )
	{
		update( yl,yr,pd,mid,r,rs );
	}
	
	pushup( l,r,rt );
}

void solve()
{
	cnt = 0;
	scanf("%lld",&n);
	for(LL i=1;i<=n;i++)
	{
		LL x1,x2,y11,y2;
		scanf("%lld %lld %lld %lld",&x1,&y11,&x2,&y2);
		u.x = x1;u.downy = y11,u.upy = y2,u.inout = 1;
		line[ ++cnt ] = u;
		yy[cnt] = y11;
		
		u.x = x2;u.downy = y11,u.upy = y2,u.inout = -1;
		line[ ++cnt ] = u;
		yy[cnt] = y2;
		
	}
	sort( yy+1,yy+cnt+1 );
	sort( line+1,line+cnt+1,cmp );
	LL length = unique( yy+1,yy+cnt+1 ) - (yy+1);
	memset( cover,0,sizeof( cover ) );
	memset( len,0,sizeof(len) );
	LL ans = 0;
	for(LL i=1;i<=cnt;i++)
	{
		ans += len[1] * ( line[i].x - line[i-1].x );
		LL yl = lower_bound( yy+1,yy+length+1,line[i].downy ) - yy;
		LL yr = lower_bound( yy+1,yy+length+1,line[i].upy ) - yy;
		LL pd = line[i].inout;
		update( yl,yr,pd,1,length,1 );
	}
	printf("%lld\n",ans);
}

signed main()
{
	//freopen("in.txt","r",stdin);
	solve();
	return 0;
}




一些相关题目:

T1 窗口的星星


参考
idea:题意不再过多赘述,首先我们思考一个问题,就是在什么条件下星星才会出现在窗户中。

设某颗星星的坐标为 ( x , y ) (x,y) (x,y),则当窗户的右上角端点的坐标出现在 ( x ∼ x + w − 1 , y ∼ y + h − 1 ) (x∼x+w−1,y∼y+h−1) (xx+w1,yy+h1) 这个范围内时,星星就会出现在窗户里。比如下图
在这里插入图片描述

因为题目中说出现在窗户边框的星星不算,所以边界坐标要 −1 ,即 ( x + w − 1 , y + h − 1 ) (x+w-1,y+h-1) (x+w1,y+h1)

于是我们可以将每个星星都扩展成一个矩形,这时我们注意到,若两个矩形之间有交集,他们便可以放在同一个窗户中
在这里插入图片描述
图中灰色的部分就是两个星星构成的矩形的交集,只要窗户的右上角端点在灰色区域内,就能同时框住两个星星。

此时我们可以将问题转化为:平面上有若干个矩形,每个矩形都带有一个权值,求在哪个坐标上权值的总和最大

接下来我们就可以使用扫描线来解决这个问题了,若当前星星的亮度值为 l l l ,则矩形的入边的权值设为 l l l,出边为 − l -l l ,此时我们只要求扫描线上的区间最大值即可得出答案,区间查询可以使用 lazy_tag 的方式实现。

注意:这里用回了普通线段树,因为我们需要的是一个一个点,不是一个个区间了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define INF 0x3f3f3f3f
#define N 101010
#define ls (p<<1)
#define rs (p<<1|1)
#define int long long
using namespace std;

int w,h,n,m,tot = 0;
LL yy[N*2],cover[N*10],ans,add[N*10];
struct node
{
	LL x,downy,upy,val;
	bool operator < (const node a)const
	{
		if( x!=a.x ) return x<a.x;
		else return val > a.val;
	}
}e[N*2];

void pushdown(int p)
{
	cover[ls] += add[p];
	cover[rs] += add[p];
	add[ls] += add[p];
	add[rs] += add[p];
	add[p] = 0;
}

void pushup(int p)
{
	cover[p] = max( cover[ls] , cover[rs] );
}

void update(int yl,int yr,int pd,int l,int r,int p)
{
	if( yl>r || yr<l ) return ;
	if( yl<=l&&r<=yr )
	{
		cover[p] += pd;
		add[p] += pd;
		return ;
	}
	if( l==r ) return ;
	pushdown( p );
	int mid = (l+r)>>1;
	
	if( yl<=mid ) update( yl,yr,pd,l,mid,ls );
	if( yr>mid ) update( yl,yr,pd,mid+1,r,rs );//这里是从mid+1开始的
	
	pushup(p);
}

void solve()
{
	tot = 0;
	int x1,x2,y1,y2,val;
	scanf("%lld %lld %lld",&n,&w,&h);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld %lld %lld",&x1,&y1,&val);
		x2 = x1 + w - 1;
		y2 = y1 + h - 1;
		e[ ++tot ] = node{ x1,y1,y2,val };
		yy[tot] = y1;
		e[ ++tot ] = node{ x2,y1,y2,-1*val };
		yy[tot] = y2;
	}
	sort( e+1,e+tot+1 );
	sort( yy+1,yy+tot+1 );
	int length = unique( yy+1,yy+tot+1 ) - ( yy+1 );
	for(int i=1;i<=tot;i++)
	{
		int yl = lower_bound( yy+1,yy+length+1,e[i].downy ) - yy;
		int yr = lower_bound( yy+1,yy+length+1,e[i].upy ) - yy;
		update( yl,yr,e[i].val,1,length,1 );
		ans = max( ans , cover[1] );
	}
	printf("%lld\n",ans);
	memset(cover,0,sizeof( cover ));
	memset(add,0,sizeof( add ));
	ans = 0;
}

signed main()
{
//	freopen("in.txt","r",stdin);
	int t;
	scanf("%d",&t);
	while( t-- ) solve();
	return 0;
}

T2 CF377D Developing Game



参考

n n n个人,每人有属性 l i , v i , r i ( l i ≤ v i ≤ r i ) l_i,v_i,r_i(l_i\leq v_i\leq r_i) li,vi,ri(liviri)
​要求选出最大的人的集合 S 使得 ∀ x , y ∈ S \forall x,y\in S x,yS l y ≤ v x ≤ r y l_y\leq v_x\leq r_y lyvxry

简单分析一下这些不等式可以发现题目的要求可以转化为: max ⁡ i ∈ S l i ≤ min ⁡ i ∈ S v i ≤ max ⁡ i ∈ S v i ≤ min ⁡ i ∈ S r i \max\limits_{i\in S} l_i\leq \min\limits_{i\in S} v_i\leq \max\limits_{i\in S} v_i\leq\min\limits_{i\in S} r_i iSmaxliiSminviiSmaxviiSminri

进一步地,我们将要求转化为找到 L , R L,R L,R,使得

max ⁡ i ∈ S l i ≤ L ≤ min ⁡ i ∈ S v i \max\limits_{i\in S} l_i\leq L\leq \min\limits_{i\in S} v_i iSmaxliLiSminvi

max ⁡ i ∈ S v i ≤ R ≤ min ⁡ i ∈ S r i \max\limits_{i\in S} v_i\leq R\leq\min\limits_{i\in S} r_i iSmaxviRiSminri
同时成立

那么我们将 ( L , R ) (L,R) (L,R)看作二维平面上的一个点,一个人就对应着一个矩形。集合中选出多个人,对 ( L , R ) (L,R) (L,R)的限制就是这些矩形的交。选出最多的人,就是要找到一个点被尽可能多的矩形覆盖

找到这个点可以用扫描线+线段树。记录最优的点的位置,输出方案时挨个判断矩形是否包含该点。
关于记录方案在主时钟有所提及

ACcode:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define N 101010
#define ls (p<<1)
#define rs (p<<1|1)
using namespace std;

struct node
{
	int x,downy,upy,val;
	bool operator < (const node a)const
	{
		if( x!=a.x ) return x<a.x;
		else return val > a.val;
	}
}line[2*N];

int yy[N*2],n,tot = 0,pos[N*8],cover[N*8],tag[N*8];//pos数组用于记录答案,就是当前这个区间cover最大值在哪

struct node2
{
	int l,v,r;
}a[N];


void pushdown(int p)
{
	cover[ls] += tag[p];
	cover[rs] += tag[p];
	tag[ls] += tag[p];
	tag[rs] += tag[p];
	tag[p] = 0;
}

void pushup(int p)
{
	if( cover[ls]>cover[rs] )//在pushup过程中记录一下这个点区间中cover值最大在哪
	{
		cover[p] = cover[ls];
		pos[p] = pos[ls];
	}
	else
	{
		cover[p] = cover[rs];
		pos[p] = pos[rs];
	}
}

void update(int yl,int yr,int pd,int l,int r,int p)
{
//	if( yr<l || yl>r ) return ;
	if( yl<=l&&r<=yr )
	{
		cover[p] += pd;
		tag[p] += pd;
		return ;
	}
	if( l==r ) return ;
	pushdown(p);
	int mid = (l+r)>>1;
	if( yl<=mid )
	{
		update( yl,yr,pd,l,mid,ls );
	}
	if( yr>mid )
	{
		update( yl,yr,pd,mid+1,r,rs );//注意从mid+1开始
	}
	pushup(p);
}

void build(int p,int l,int r)
{
	if( l==r ) 
	{
		pos[p] = l;
		return ;
	}
	int mid = (l+r)>>1;
	build( ls,l,mid );
	build( rs,mid+1,r );
	pos[p] = pos[p<<1];//用build函数主要是为了初始化pos数组
}

void solve()
{
	memset(cover,0,sizeof(cover));
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int l,r,v;
		scanf("%d %d %d",&l,&v,&r);
		line[ ++tot ] = node{ l,v,r,1 };
		yy[tot] = v;
		line[ ++tot ] = node{ v,v,r,-1 };
		yy[tot] = r;
		a[i] = node2{ l,v,r };
	}
	sort(line+1,line+tot+1);
	sort(yy+1,yy+tot+1);
	int length = unique( yy+1,yy+tot+1 ) - (yy+1);
	build( 1,1,length );
	int ans = 0,L,R;
	for(int i=1;i<=tot;i++)
	{
		int yl = lower_bound( yy+1,yy+length+1,line[i].downy ) - yy;
		int yr = lower_bound( yy+1,yy+length+1,line[i].upy ) - yy;
		update( yl,yr,line[i].val,1,length,1 );
		if( ans < cover[1] )
		{
			ans = cover[1];
			L = line[i].x;
			R = pos[1];
//			cout<<L<<" "<<yy[R]<<endl;
		}
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
	{
		if( a[i].l<=L&&L<=a[i].v&&a[i].v<=yy[R]&&yy[R]<=a[i].r )
		{
			printf("%d ",i);
		}
	}
	printf("\n");
}

int main()
{
//	freopen("in.txt","r",stdin);
	solve();
	return 0;
}




T3 (周长并模板题)[USACO5.5]矩形周长Picture

idea:扫两边即可,相邻两次修改的区间覆盖长度差(len的差)加起来就是答案。

ACCode:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
#define N 101010
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;

struct node
{
	int x,down,up,val;
	bool operator < (const node a)const
	{
		if( x==a.x ) return val > a.val;
		else return x < a.x;
	}
}line1[N*2],line2[N*2];

int n,x1,x2,y1,y2,tot1 = 0,tot2 = 0,yy[N*2],xx[N*2],ans = 0,len[N*8];
int cover[N*8];

void pushup(int l,int r,int rt,int seg[])
{
	if( cover[rt]>0 ) len[rt] = seg[r] - seg[l];
	else if( l+1==r ) len[rt] = 0;
	else len[rt] = len[ls] + len[rs];
	return ;	
}

void update(int yl,int yr,int pd,int l,int r,int rt,int seg[])
{
	if( yl>r || yr<l ) return ;
	if( yl<=l && r<=yr )
	{
		cover[rt] += pd;
		pushup(l,r,rt,seg);	
		return ;  
	}
	if( l+1==r ) return ;
	int mid = (l+r)>>1;
	if( yl<=mid ) update( yl,yr,pd,l,mid,ls,seg );
	if( yr>mid ) update( yl,yr,pd,mid,r,rs,seg );
	pushup(l,r,rt,seg);
}

void solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
		line1[ ++tot1 ] = node{ x1,y1,y2,1 };
		yy[ tot1 ] = y1;
		line1[ ++tot1 ] = node{ x2,y1,y2,-1 };
		yy[ tot1 ] = y2;
		
		line2[ ++tot2 ] = node{ y1,x1,x2,1 };
		xx[ tot2 ] = x1;
		line2[ ++tot2 ] = node{ y2,x1,x2,-1 };
		xx[ tot2 ] = x2;
		
	}
	sort(line1+1,line1+1+tot1);
	sort(line2+1,line2+1+tot2);
	sort( yy+1,yy+tot1+1 );
	sort( xx+1,xx+tot2+1 );
	int leny = unique( yy+1,yy+tot1+1 ) - (yy+1);
	int lenx = unique( xx+1,xx+tot2+1 ) - (xx+1);
	
	int last;
	last = 0;
	for(int i=1;i<=tot1;i++)
	{
		int yl = lower_bound( yy+1,yy+leny+1,line1[i].down ) - yy;
		int yr = lower_bound( yy+1,yy+leny+1,line1[i].up ) - yy;
		update( yl,yr,line1[i].val,1,leny,1,yy );
		ans += abs( len[1] - last );
		last = len[1];
	}
	
	last = 0;
	memset(cover,0,sizeof(cover));
	memset(len,0,sizeof(len));
	for(int i=1;i<=tot2;i++)
	{
		int xl = lower_bound( xx+1,xx+lenx+1,line2[i].down ) - xx;
		int xr = lower_bound( xx+1,xx+lenx+1,line2[i].up ) - xx;
		update( xl,xr,line2[i].val,1,lenx,1,xx );
		ans += abs( len[1] - last );
		last = len[1];
	}
	
	cout<<ans;
}

int main()
{
	//freopen("in.txt","r",stdin);
	solve();
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值