扫描线学习笔记

矩形面积并

给定平面直角坐标系中 n n n 个四边均平行于坐标轴的矩形,求它们的面积并(即面积重叠的部分只计算一次)
通常 1 ≤ n ≤ 1 e 5 1 \leq n \leq 1e5 1n1e5

暴力

可以先求出每个矩形的面积求和,再枚举任意两个矩形的并,在答案中减去。
枚举矩形是 O ( n 2 ) O(n^2) O(n2) 的,无法胜任。代码很简单,就不放了

正解

(为了方便起见,我们参照数组,把左上角记为 ( 1 , 1 ) (1,1) (1,1) ,第 x x x y y y 列记为 ( x , y ) (x,y) (x,y) 。这对答案没有影响)
让我们重新看待两个相交的矩形
在这里插入图片描述

如上图,我们可以把两个相交的矩形(如 ( a ) (a) (a) )看作三个(多个)无重叠的矩形(如 ( b ) (b) (b) )。这些矩形求和比较好做。我们可以认为矩形的左右两边列的差为矩形的高,这是好求的,接下来就是求宽的问题了
此时定义入边为矩形左边的那条边,出边为右边的那条边(如果你是横着分割的话,那么入边为上边的那条边,出边为下边那条边)。令 l e n len len 表示从左到右扫到当前矩形时的所有矩形的宽度之和, l e n len len 的初值为 0 0 0 ,此时不难发现当扫到一条入边的时候 l e n len len 会增加,扫到出边时 l e n len len 会减少。于是用值域线段树维护当前的宽的和,统计答案时用这个值乘上高即可。具体而言,扫到区间 ( l , r ) (l,r) (l,r) 时将线段树上 ( l , r ) (l,r) (l,r) 的值增加(或减少) 1 1 1 ,统计时只用看哪些区域有值就可以了。详见代码(洛谷模板题

#include <bits/stdc++.h>
using namespace std;
#define sort stable_sort
#define rep(i,n) for(int i = 1;i <= n;i++)
#define int long long
#define swap(x,y) (x ^= y ^= x ^= y)
#define debug cout<<"Help!\n"
const int N = 1e6 + 5;
int n,tot,X1,X2,Y1,Y2,ans;
vector <int> v;
struct scanline{
	int l,r,h,in_out;
	inline bool operator < (const scanline & b) const {
		return h < b.h;
	}
}line[N];
struct Seg{
	#define ls(u) (u << 1)
	#define rs(u) (u << 1 | 1)
	struct Tree{
		int len,cnt;//区间的总宽度,目前覆盖该区间的矩形个数
	}t[N<<2];
	inline void pushup(int u,int l,int r){
		if(t[u].cnt) t[u].len = v[r] - v[l-1];
		else t[u].len = t[ls(u)].len + t[rs(u)].len;
	}
	void update(int u,int L,int R,int l,int r,int x){
		if(l <= L && R <= r){
			t[u].cnt += x;
			pushup(u,L,R);
			return;
		}
		int mid = (L + R) >> 1;
		if(l <= mid) update(ls(u),L,mid,l,r,x);
		if(r > mid) update(rs(u),mid + 1,R,l,r,x);
		pushup(u,L,R);
	}
}seg;
inline int find(int x){
	return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	rep(i,n){
		cin>>X1>>Y1>>X2>>Y2;
		//小心一下。作者写的时候脑子抽了在下面一句把 x1 y1,x2 y2 分别写反了,虽然不影响正确性,但是在写的时候还是注意逻辑一惯性比较好
		line[++tot] = {Y1,Y2,X1,+1},line[++tot] = {Y1,Y2,X2,-1};//如果是入边赋 +1 的权值,出边赋 -1 的权值
		v.emplace_back(Y1),v.emplace_back(Y2);//这题需要离散化
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	sort(line + 1,line + tot + 1);//按照区间的位置(即理应当的 y 值)排序方便扫描
	rep(i,tot){
		ans += seg.t[1].len * (line[i].h - line[i-1].h);//目前的宽度之和 * 高
		seg.update(1,1,v.size(),find(line[i].l),find(line[i].r) - 1,line[i].in_out);//更新宽度
	}
	cout<<ans;
	return 0;
}

矩形周长并

给定平面直角坐标系中 n n n 个四边均平行于坐标轴的矩形,求它们的周长(所有矩形合并后的边长称为周长)

解法

其实与矩形面积并类似,但是可以再扫描竖线的时候顺便计算横线
有点复杂,但其实就是加上统计区间内有多少独立的横线即可。在这里不好讲清楚,看代码里的注释吧
以下是一道 模板题 的代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
const int N = 2e4 + 5;
struct scanline{
	int l,r,h,in_out;
}line[N];
inline bool cmp(scanline a,scanline b){
	if(a.h == b.h) return a.in_out > b.in_out;
	return a.h < b.h;
}
int n,tot,minn = 1e4,maxx = -1e4,X1,Y1,X2,Y2,ans,lst;
struct node{
	bool l,r;//该区间左右端点是否被覆盖
	int num,tag,length;//该区间横线数量,该节点是否有效,该区间的有效长度
}t[N<<2];
inline void pushup(int u,int l,int  r){
	if(t[u].tag){
		t[u].l = t[u].r = true;
		t[u].num = 1,t[u].length = r - l + 1;
		return;
	}
	if(l == r){
		t[u].num = t[u].length = t[u].l = t[u].r = false;
		return;
	}
	t[u].l = t[u<<1].l,t[u].r = t[u<<1|1].r;
	t[u].length = t[u<<1].length + t[u<<1|1].length,t[u].num = t[u<<1].num + t[u<<1|1].num;
	t[u].num -= t[u<<1].r & t[u<<1|1].l;
}
void update(int u,int l,int r,int L,int R,int add){
	if(l <= L && R <= r){
		t[u].tag += add;
		pushup(u,L,R);
		return;
	}
	int mid = (L + R) >> 1;
	if(l <= mid) update(u << 1,l,r,L,mid,add);
	if(r > mid) update(u << 1 | 1,l,r,mid + 1,R,add);
	pushup(u,L,R);
}
template <typename T>
inline void read(T & x){
	x = 0;
	T f = 1;
	char c;
	for(c = getchar();!isdigit(c);c = getchar()) if(c == '-') f = -1;
	for(;isdigit(c);c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
	x *= f;
}
template <typename T>
inline void write(T x){
	if(x / 10) write(x / 10);
	putchar((x % 10 + '0'));
}
int main(){
	read(n);
	rep(i,n){
		read(X1),read(Y1),read(X2),read(Y2);
		minn = min(minn,X1),maxx = max(maxx,X2);
		line[++tot] = (scanline){X1,X2,Y1,1},line[++tot] = (scanline){X1,X2,Y2,-1};//记录入边出边
	}
	sort(line + 1,line + tot + 1,cmp);
	rep(i,tot){
		if(line[i].l < line[i].r) update(1,line[i].l,line[i].r - 1,minn,maxx - 1,line[i].in_out);//如果该矩形合法
		ans += t[1].num * 2 * (line[i+1].h - line[i].h) + abs(lst - t[1].length);//重点:目前的横线数量 * 横线长度 + 竖线长度的变化
		lst = t[1].length;
	}
	write(ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值