扫描线

寒假集训前几道题都和扫描线有关,写一篇博客吧。

扫描线

特点

扫描线是一种求矩形面积并/周长并的好方法。

假设有一条扫描线从一个图形的下方扫向上方(或者左方扫到右方),那么通过分析扫描线被图形截得的线段就能获得所要的结果。该过程可以用线段树进行加速。

导入问题

现在给你几个矩形,请你求出它们的面积并/周长并。

面积并

洛谷例题
举一个简单的例子,如图(嫖的):
在这里插入图片描述
问题是如何模拟扫描线从上往下(或者从左往右,方向据题目和方法),并且得到当前扫描线截到的长度。

假设扫描线上下扫,遇到边界停下来,如图:
在这里插入图片描述
那么对图形面积产生影响的元素就是这些横边左右端点的坐标。

为了计算出截线段长度,可以将横边赋上不同的权值,具体为:对于一个矩形,其下边权值为 1 1 1,上边权值为 − 1 −1 1
在这里插入图片描述
这样操作以后,就可以和线段树扯上关系。把所有端点在 x x x轴上离散化。
在这里插入图片描述
在这个例子中, 4 4 4个端点的纵投影总共把 x x x轴切割成了 5 5 5部分。取中间的 3 3 3部分线段,建立一棵线段树,其每个端点维护一条线段(也就是一个区间)的信息:

  1. 该线段被多少个矩形所覆盖
  2. 该线段内被整个图形所截的长度是多少
    在这里插入图片描述
    显然,只要一条线段被覆盖,那么它肯定被图形所截。所以,整个问题就转化为了一个区间查询问题,即:每次将当前扫描线扫到的边对应的信息,按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积。这就可以用线段树来实现了。

模拟过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们已经知道,这棵线段树的每个节点都对应了一条线段。同样地,建树的时候已经保证了左右儿子的区间不会重合。但是看这样两条相邻线段: [ 1 , 2 ] [1,2] [1,2] [ 2 , 3 ] [2,3] [2,3], [ 1 , 2 ] ∪ [ 2 , 3 ] = ( 2 ) P [1,2]\cup[2,3]=(2)P [1,2][2,3]=(2)P(打不来大括号,用小括号代替下),也就是说左儿子的右端点和右儿子的左端点其实是重合的。

怎么解决这个问题呢?考虑线段树维护的信息不变,改变区间和横边的映射关系,即:节点 x x x对应 [   X [ T r e e [ x ] . l ] , X [ T r e e [ x ] . r + 1 ]   ] [\ X[Tree[x].l], X[Tree[x].r+1]\ ] [ X[Tree[x].l],X[Tree[x].r+1] ]这条横边。自此,面积并就解决了。

Code:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 1000005
#define LL long long
#define Int register int
using namespace std;
inline void read(LL &x)
{
	x = 0;
	LL f = 1;
	char s = getchar();
	while (s < '0' || s > '9')
	{
		if (s == '-')
			f = -1;
		s = getchar();
	}
	while (s >= '0' && s <= '9')
	{
		x = (x << 3) + (x << 1) + (s ^ 48);
		s = getchar();
	}
	x *= f;
}
LL X[MAXN << 1];
struct ScanLine
{
	LL l, r, h, Mark;
	ScanLine(){}
	ScanLine(LL L,LL R,LL H,LL MARK)
	{
		l = L;
		r = R;
		h = H;
		Mark = MARK;
	}
	friend bool operator <(ScanLine x,ScanLine y)
	{
		return x.h < y.h;
	}
}Line[MAXN << 1];
struct SegMentTree
{
	LL l, r, Cov, Len;
}Tree[MAXN << 2];
void Update(LL x)
{
	if (Tree[x].Cov)
		Tree[x].Len = X[Tree[x].r + 1] - X[Tree[x].l];
	else Tree[x].Len = Tree[x << 1].Len + Tree[x << 1 | 1].Len;
}
void Build(LL x,LL l,LL r)
{
	Tree[x].l = l, Tree[x].r = r;
	Tree[x].Cov = Tree[x].Len = 0;
	if (l == r)
		return ;
	LL Mid = (l + r) / 2;
	Build(x << 1, l, Mid);
	Build(x << 1 | 1, Mid + 1, r);
}
void EditTree(LL x,LL l,LL r,LL Kind)
{
	if (X[Tree[x].r + 1] <= l || X[Tree[x].l] >= r)
		return ;
	if (X[Tree[x].l] >= l && X[Tree[x].r + 1] <= r)
	{
		Tree[x].Cov += Kind;
		Update( x );
		return ;
	}
	EditTree(x << 1, l, r, Kind);
	EditTree(x << 1 | 1, l, r, Kind);
	Update( x );
}
int main()
{
	LL n;
	read( n );
	for (Int i = 1; i <= n; ++ i)
	{
		LL a, b, c, d;
		read( a ); read( b );
		read( c ); read( d );
		X[i * 2 - 1] = a, X[i * 2] = c;
		Line[i * 2 - 1] = ScanLine(a, c, b, 1);
		Line[i * 2] = ScanLine(a, c, d, -1);
	}
	n <<= 1;
	sort(Line + 1, Line + 1 + n);
	sort(X + 1, X + 1 + n);
	LL tot = unique(X + 1, X + 1 + n) - X - 1;
	// 离散化,X映射 Line的 x 
	Build(1, 1, tot - 1);
	LL Ans = 0;
	for (Int i = 1; i < n; ++ i)
	{
		EditTree(1, Line[i].l, Line[i].r, Line[i].Mark);
		//printf("%lld %lld\n", Line[i + 1].h, Line[i].h);
		Ans += Tree[1].Len * (Line[i + 1].h - Line[i].h);
	}
	printf("%lld", Ans);
	return 0;
}

周长并

模板题

举个例子:
在这里插入图片描述
看图中的三条扫描线,不难发现,纵边的总长度为   2 ∑ ∗ 扫 到 的 线 段 条 数 ∗ 线 段 的 高 度 差 \ 2\sum * 扫到的线段条数*线段的高度差  2线线
在这里插入图片描述
又会发现, 横 边 的 总 长 = ∣ 上 一 次 扫 描 线 截 到 的 总 长 度 − 这 次 扫 描 线 截 到 的 总 长 度 ∣ 横边的总长=\mid 上一次扫描线截到的总长度-这次扫描线截到的总长度 \mid =线线

所以和面积并比起来,周长并中的线段树还要维护线段的条数。另外,横边和纵边需分别计算。

Code:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值