P5490 【模板】扫描线

1,076 阅读3分钟

题目链接

【模板】扫描线
题目大意:在平面直角坐标系中求所给矩形面积的并

思路

一个矩形的面积=长*宽。
对于题目中所给的不规则图形,考虑把它们分成若干小矩形来计算面积。

样例如图所示 如图,把两个相交的矩形用P1P4P1\sim P4四条线分为三块,分别求他们的面积得到答案。

如何计算一个小矩形的面积

首先记录下所有平行于yy的边(平行于x轴也可以,这取决于扫描的方向)。

当扫描到一条边时,就可以计算扫过矩形的面积。维护当前这个矩形在延yy轴方向上被矩形覆盖的长度lenlen(也就是图中竖边的总长)。 记当前这条竖边与上一条竖边的横坐标之差为kk
ans+=len×kans+=len\times k
原理就是lenlen为竖边长度,kk为横边长度。
kk显然容易得到,可以在输入的时候维护一下所有边的横坐标pos,pospos为关键字排序后保证从左向右扫描。
该题的难度就在于动态维护lenlen

如何维护lenlen

lenlen的维护,即区间修改(也就是每扫描到一个新边(进入一个新的矩形)都要维护该边所在区间的答案)与区间查询(查询当前扫描到的矩形延y轴的长度)。

总的来说,就是要用线段树来维护线段(而非点)。
线段树 红字表示线段树的区间。分别为第1~4条线段。

struct Tree{
	int fg,len;
}t[4*N];

一个线段树的结点记录当前线段是否被完全覆盖(fgfg)和这一段上被矩形覆盖的长度(lenlen)。
假设有一条线,从yy轴向右扫描,每当扫到一个新的边,有如下两种情况:

  1. 扫到了一个矩形的左边(将要进入一个新的矩形)。
  2. 扫到了一个矩形的右边(将要退出当前的矩形)。

每次扫到一个矩形的左边,就让对应线段的fg++fg++,扫到矩形的右边fgfg--。易证明fg0fg\geq0。当fgfg00时,当前区间没有被完全覆盖。否则就被完全覆盖。
如样例中第一个扫描到的竖边为ABAB,上下端点分别为100200100,200。由上图可知,实际维护的线段是(100~199),则对 (100~199)这个区间进行维护。
继续扫描,扫到新边A1B2A1B2,则ans+=t[1].len(150100)ans+=t[1].len*(150-100)。 这样扫描下去就可以获得最终答案。

对于一个结点p的修改,有如下两种情况:

  1. pp被完全覆盖(fg0fg\neq0),也就是说当前的线段被完全包含于新扫描到的边,只需要用当前线段的右端点减去左端点即可。但是要记得,右端点是当前区间最右边线段的序号+1 这一点非常关键。
  2. pp没有被完全覆盖(fg==0fg==0),那么它的len就来自左右两个儿子,有t[p].len=t[lc].len+t[rc].lent[p].len=t[lc].len+t[rc].len

因为数据范围过大,需要对数据离散化

代码如下

#include<iostream>
#include<algorithm>
#include<cstdio>
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
using namespace std;
const int N=((1E5+10)*2);
ll n,raw[N],len[4*N],t[4*N];
ll ans;
inline ll read()
{
	ll ret=0;char ch=getchar();
	while(ch<'0' || ch>'9')ch=getchar();
	while(ch>='0' && ch<='9')ret=ret*10+ch-'0',ch=getchar();
	return ret; 
}
struct LINE{
	ll st,ed,hi,ud;		//ud -1:上边 1:下边 
}l[N];
void push_up(int p,int l,int r)
{
	if(t[p])len[p]=raw[r+1]-raw[l];
	else if(l==r)len[p]=0;
	else len[p]=len[lc]+len[rc];
}
void add(int p,int l,int r,int al,int ar,int val)
{
	if(al<=l && ar>=r)
	{
		t[p]+=val;
		push_up(p,l,r);
		return;
	}
	ll mid=(l+r)>>1;
	if(al<=mid)add(lc,l,mid,al,ar,val);
	if(ar>=mid+1)add(rc,mid+1,r,al,ar,val);
	push_up(p,l,r);
}
bool cmp(const LINE &a,const LINE &b)
{
	return a.hi<b.hi;
}
int main()
{
//	freopen("d.in","r",stdin);
	n=read();
	for(int i=1,x_,x__,y_,y__;i<=n;++i)
	{
		x_=read();y_=read();x__=read();y__=read();
		l[i]=(LINE){x_,x__,y_,1};
		l[i+n]=(LINE){x_,x__,y__,-1};
		raw[i]=x_;
		raw[i+n]=x__;
	}
	n<<=1;
	sort(raw+1,raw+n+1);
	ll nn=unique(raw+1,raw+n+1)-(raw+1);
	sort(l+1,l+n+1,cmp);
	for(int i=1;i<n;++i)
	{
//		cout<<l[i].st<<" "<<l[i].ed<<" ";
		ll lf=lower_bound(raw+1,raw+nn+1,l[i].st)-raw;
		ll rt=lower_bound(raw+1,raw+nn+1,l[i].ed)-raw;
//		cout<<lf<<" "<<rt-1<<" "<<l[i].ud<<" ";
		if(lf<rt)
		add(1,1,nn,lf,rt-1,l[i].ud);
		ans+=len[1]*(l[i+1].hi-l[i].hi);
//		cout<<len[1]<<endl;
	}
	cout<<ans<<endl;
}