扫描线

luogu板子传送
现在我们有许多许多矩形,并且我们知道每个矩形左上角和右下角的坐标,我们需要求这些矩形的面积并

扫描线的思路

假设我们现在只有两个矩形

没错就是洛谷样例
我们假设有一条线从x轴开始,往上扫描,那这条线肯定会扫描到所有矩形的所有底边
我们把这些底边标出来

每两条底边之间,对答案的贡献肯定就是它们的高度差\(\times\)当前的底的长度
比如说,第一次就会将这一部分加入到答案里

第二次就会将这一部分加入答案里

所以我们要做到就是维护底的长度
在上图中,我们注意到\(x\)可以被分成3个有用的部分,就像这样

我们给每个部分设一个\(cnt\)值,来表示这个部分当前是否在要计算的底边里
\(cnt_i > 0\)时,表示这个部分在里面,反之则不在
当前用来计算的底边的长度则为所有\(cnt>0\)的部分的长度之和
如果我们的扫描线扫描到的底边是下底,则其包含的部分的\(cnt\)\(+1\)
若当前的扫描线是某个矩形的上底,则该矩形底边覆盖的部分的\(cnt\)值都-1
若当前\(cnt_i>0\),则说明\(i\)肯定被某个矩形的下底覆盖且当前扫描线还没有到达该矩形的上底,所以\(i\)肯定会被算到底边里
问题来了,我们应该怎么去实现呢?
我们观察一下上面的图,可以看到每个部分都是一条线段
同时,每个矩形的底边也是线段,并且会覆盖整数个部分
我们还要对这些覆盖的部分进行加减操作
很像线段树有木有?
所以我们用线段树来搞\(cnt\)的维护

线段树的搞法

我们先用\(X\)数组记录下所有的横坐标,然后离散化,此时\(X[i]\)表示部分\(i\)的左端点的\(x\)值。
若不相同的\(x\)坐标一共有\(m\)个,则会有\(m-1\)个部分。我们在建线段树时,\(cnt[l,r]\)表示第\(l\)个部分一直到第\(r\)个部分这个整体是否被完全覆盖(\(cnt>0\)表示被完全覆盖)
为了快速得出所有\(cnt>0\)的区间的总长度,我们再用\(sgt[l,r]\)表示从第\(l\)个部分一直到第\(r\)个部分中,\(cnt>0\)的区间总长度(注意上面的\(cnt\)要求全被覆盖,这里\(sgt\)可以有中间没有被覆盖的区间)。若\(cnt[l,r]>0\),则\(sgt[l,r]=X[r+1]-X[l]\),因为第\(r\)个部分的右端点是\(X[r+1]\)
\([l,r]\)并没有完全被覆盖,则\(sgt[l,r]\)由它的两个儿子更新而来
似乎就没什么可以口胡的了
来康康代码吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<cstdlib>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
	char ch=getchar();
	int x=0;bool f=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return f?-x:x;
}
int n,cl,cx;
ll ans,sgt[1600009],wa[400009],cnt[1600009];//这里wa就是上文的X,以及注意开longlong
struct sl{
	int y,xl,xr,d;//y记录当前扫描线纵坐标,xl为左端点,xr为右端点,d=1或-1,表示增加的cnt
}lin[400009];
bool cmp(sl a,sl b)
{
	return a.y<b.y;
}
void add(int k,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		cnt[k]+=d;
		if(cnt[k]>0) sgt[k]=wa[r+1]-wa[l];
		else if(l==r) sgt[k]=0;//若当前区间只包含一个部分且未被覆盖,则总长度为0
		else sgt[k]=sgt[k<<1]+sgt[k<<1|1];
		return ;
	}//由于cnt不能通过左右儿子更新,所以就没有pushdown了
	int mid=(l+r)>>1;
	if(x<=mid) add(k<<1,l,mid,x,y,d);
	if(mid<y) add(k<<1|1,mid+1,r,x,y,d);
	if(cnt[k]>0) sgt[k]=wa[r+1]-wa[l];
	else if(l==r) sgt[k]=0;
        else sgt[k]=sgt[k<<1]+sgt[k<<1|1];
}
int main()
{
	n=read();int ccx=0;
	for(int i=1;i<=n;i++)
	{
               int x1=read(),yy=read(),x2=read(),yyy=read();
	       lin[++cl].xl=x1;lin[cl].xr=x2;lin[cl].y=yy;lin[cl].d=1;
               lin[++cl].xl=x1;lin[cl].xr=x2;lin[cl].y=yyy;lin[cl].d=-1;
	       wa[++ccx]=x1;wa[++ccx]=x2;
	}
	sort(wa+1,wa+ccx+1);
	cx=unique(wa+1,wa+ccx+1)-wa-1;//cx相当于上述的m
	sort(lin+1,lin+cl+1,cmp);
	int h=0,sum=0;
	for(int i=1;i<cl;i++)
	{
		h=lin[i].y;
		int lft=lower_bound(wa+1,wa+cx+1,lin[i].xl)-wa;
		int rgt=lower_bound(wa+1,wa+cx+1,lin[i].xr)-wa-1;//-1之后才是对应的第r个部分
		add(1,1,cx-1,lft,rgt,lin[i].d);//总共有cx-1个部分
		ans+=sgt[1]*(lin[i+1].y-lin[i].y);
	}
	printf("%lld\n",ans);
}


来几道练手题叭(待补充)
窗口的星星

题解 以每个星星为左下角,建立权值为l,长、宽为W,H的矩形,答案为最大的矩形交权值 在建线段树时,因为我们要处理矩形交,所以用[l,r]表示X[l]~X[r]

矩形周长

题解类似物 这个和板子差不多,暴力思路上下左右各扫一遍就好了,反正也不会T(划掉)
posted @ 2020-06-11 20:42  千载煜  阅读(161)  评论(0编辑  收藏  举报