扫描线/矩形面积并

扫描线的思想很早就会了,所以一直以为这个板子自己会了,但实际上并没有,这题还是不简单。

首先,扫描线的思想很简单,就是当我们要处理多维的问题时,我们可以对其中一个维度进行排序,然后用数据结构维护剩下的维度,这样可以问题降低一个来考虑。当然实际上我们没有凭空吃掉一个维度,降低的维度实际上是被我们放到时间维上了。

比如我们如果要求一个不规则形状的二维图像的面积,我们可以对x轴维度排序,然后y轴在任意时刻都只是一维上的一堆线段而已,我们控制这些线段沿x轴方向移动,记录扫过的面积,就是图形面积了。这个过程类似于一条线扫过整个图形,我们只用在扫的过程中维护这个线上的线段总长度,因此这个思想也被称为扫描线

这里oiwiki的图非常形象,建议去看oiwiki
扫描线/矩形面积并_第1张图片
然而理解了这个思想还不足以解决矩形面积并这个问题,因为我们剩下的那个维度是要用数据结构解决的,具体来说,我们要解决这样一个问题:每次对一个区间进行加减,问整个区间里不为0的位置的个数?

首先这应该是可以用线段树,但是并不能在区间加线段树的模板上略加修改得到,因为我们在区间加之后还要维护每次区间加对区间内非0位置个数的影响,为了保证复杂度,这个维护必须是 O ( 1 ) O(1) O(1)的,你仔细想想就知道这仅在区间加线段树的框架内很难做到。

所以必须转换模式,注意到一般数据给出的线段可能范围很大,但是线段个数肯定不会超过 1 e 6 1e6 1e6左右,也就是整个区间会被划分成不超过 1 e 6 1e6 1e6个区间,我们每次对一个线段的范围内进行加减,实际上只是对这些区间的组合进行修改,并不会出现一个区间被修改一半的情况。

这启示我们把每个最小区间作为线段树叶子节点保存的信息,而不像原始线段树一样把每个点作为叶子节点的信息,具体来说,我们仍然可以在每个节点上维护一个 s u m sum sum标记记录这个区间被加的次数,以及一个 l e n len len表示这个区间内被覆盖的区间的总长度,显然如果 s u m > 0 sum>0 sum>0这个区间内所有小区间都被覆盖了,那么这个区间的 l e n len len就是这个这个区间的总长度。如果 s u m = 0 sum=0 sum=0那么这个区间可能内部只有部分小区间被覆盖了,总长度是左右儿子的长度和。

这里很容易搞混的一点是:线段树的区间 [ l , r ] [l,r] [l,r],对应的并不是原始数轴上的区间,而是下标在 [ l , r ] [l,r] [l,r]范围内的所有最小区间,再次强调:这个线段树叶子上保存的基本元素,是原始数轴上的所有最小区间。

举个例子:数轴上有 [ 1 , 2 ] [ 2 , 3 ] [ 3 , 4 ] [1,2][2,3][3,4] [1,2][2,3][3,4]三个区间,线段树上 [ 1 , 1 ] [1,1] [1,1]节点保存的是 1 1 1号区间也就是 [ 1 , 2 ] [1,2] [1,2] [ 1 , 2 ] [1,2] [1,2]节点保存的是 1 , 2 1,2 1,2号区间也就是 [ 1 , 2 ] [ 2 , 3 ] [1,2][2,3] [1,2][2,3]

最后数据范围很大,需要离散化,我们建树的范围使用数据个数,也就是不同线段端点的个数,但是查询和计算 l e n len len时使用原始数据,我们可以把原始数据排序去重放到一个数组里,然后用下标访问

这里实现的时候还有个神秘 b u g bug bug是不能使用重载 n o d e + node+ node+号的线段树板子,因为 p u s h u p pushup pushup的时候,需要看当前节点的 s u m sum sum标记,并且这个标记不是由左右儿子合并得到的,是看当前节点的原始值,所以如果重载运算符的话,会先用左右儿子的信息计算出当前节点的 s u m sum sum,再看这个 s u m sum sum来计算 l e n len len,但用左右儿子计算出来的 s u m sum sum是无意义的,因此 l e n len len的结果也是错的。所以这里老老实实写一下 p u s h u p pushup pushup就完了。另外每次查询都是查整个区间,所以其实不用写 q u e r y query query函数,直接 t r [ 1 ] . l e n tr[1].len tr[1].len就行了。这里把重载加号的代码也保留了,但没有调用,起一个警示作用。

最后就是扫描线的具体实现,这个不谈,把每个数据拆成插入和删除两次就行,也就是一个矩形 x 1 , y 1 , x 2 , y 2 x1,y1,x2,y2 x1,y1,x2,y2,在 y 1 插入, y 2 删除 y1插入,y2删除 y1插入,y2删除,最后把所有操作按 y y y升序排序

int allx[N];
struct Tree{
	#define ls u<<1
	#define rs u<<1|1
	struct Node{
		int l,r,sum,len;
		
		Node operator+(const Node &o){
			Node res;
			res.l=l;
			res.r=o.r;
			if(res.sum){
				res.len=allx[res.r+1]-allx[res.l];
			}
			else{
				res.len=len+o.len;
			}
			return res;
		}
	}tr[N<<2];
	
	void pushup(int u){
		int l=tr[u].l,r=tr[u].r;
		if(tr[u].sum){
			tr[u].len=allx[r+1]-allx[l];
		}
		else{
			tr[u].len=tr[ls].len+tr[rs].len;
		}
	}
	
	void build(int u,int l,int r){
		tr[u]={l,r,0,0};
		if(l==r)	return;
		int mid=(l+r)>>1;
		build(ls,l,mid);	build(rs,mid+1,r);
		pushup(u);
	}
	
    void modify(int u,int L,int R,int val){
    	int l=tr[u].l,r=tr[u].r;
    	if(allx[r+1]<=L||allx[l]>=R)return;
    	
        if(allx[l]>=L&&allx[r+1]<=R){   	
			tr[u].sum+=val;
            pushup(u);
            return ;
        }
        else{
            modify(ls,L,R,val);
            modify(rs,L,R,val);
            pushup(u);  
        }
    }
	
	Node query(int u,int l,int r){
        if(l<=tr[u].l&&tr[u].r<=r)    return  tr[u];
        int mid=(tr[u].l+tr[u].r)>>1;
        if(r<=mid)return query(ls,l,r);
        if(l>mid)return query(rs,l,r);
        return query(ls,l,r)+query(rs,l, r);
	}
}t;
void solve(){
	cin>>n;
	vvi a;
	rep(i,1,n){
		int x1,x2,y1,y2;
		cin>>x1>>y1>>x2>>y2;
		allx[2*i]=x1;
		allx[2*i-1]=x2;
		a.push_back({x1,x2,y1,1});
		a.push_back({x1,x2,y2,-1});
	}
	sort(a.begin(),a.end(),[&](vi &x,vi &y){
		return x[2]

你可能感兴趣的:(android,算法)