[线段树系列 #1] 扫描线

[线段树系列 #1] 扫描线

核心思路

将矩形分割为这样:

对于每条竖向边,将y轴上的区间作为数据结构维护对象,从小到大遍历每个x(即可理解为从左到右扫描一遍),即可将分割图形的行为抽象为简单的数据结构操作。

例题

例题:P5490 【模板】扫描线 & 矩形面积并

以扫描线从上到下扫为例具体来说,是线段树存储当前线覆盖的长度(横向),再乘上当前线与上条线

将读入的矩形的竖向边存起来并离散化

通过cov数组记录是否该段需计入答案

ll n, raw[N], ans;
unordered_map <ll, ll> val;
struct Line{
	ll x, l, r, v;
}a[N];
struct SegmentTree{
	ll l, r, cov, len;
}t[4*N];

bool cmp(Line x, Line y){
	return x.x < y.x;
}
void pushup(ll p){
	if(t[p].cov) t[p].len = raw[t[p].r+1]-raw[t[p].l]; // +1的原因是每个节点存的是l~r+1
	else t[p].len = t[p*2].len+t[p*2+1].len;
}
void build(ll p, ll l, ll r){
	t[p].l = l, t[p].r = r;
	if(l == r) return;
	ll mid = (l+r)>>1;
	build(p*2, l, mid);
	build(p*2+1, mid+1, r);
}
void update(ll p, ll l, ll r, ll v){
	if(l <= t[p].l && t[p].r <= r){
		t[p].cov += v;
		pushup(p);
		return;
	}
	ll mid = (t[p].l+t[p].r)>>1;
	if(l <= mid) update(p*2, l, r, v);
	if(mid < r) update(p*2+1, l, r, v);
	pushup(p);
}
void init(){
	n = read();
	for(ll i = 1;i <= n;i++){
		ll xa = read(), ya = read(), xb = read(), yb = read();
		a[i*2-1].x = xa, a[i*2].x = xb;
		raw[i*2-1] = a[i*2-1].l = a[i*2].l = ya;
		raw[i*2] = a[i*2-1].r = a[i*2].r = yb;
		a[i*2-1].v = 1, a[i*2].v = -1;
	}
	n *= 2;
	sort(raw+1, raw+1+n);
	sort(a+1, a+1+n, cmp);
	ll m = unique(raw+1, raw+1+n)-(raw+1);
	for(ll i = 1;i <= m;i++) val[raw[i]] = i;
	build(1, 1, m-1);
}
void solve(){
	init();
	for(ll i = 1;i < n;i++){
		update(1, val[a[i].l], val[a[i].r]-1, a[i].v);
		ans += t[1].len*(a[i+1].x-a[i].x);
	}
	write(ans);
}

易错点

  1. 线段树上节点对应区间与线段进行映射对应时,由于两条相邻线段的端点有可能重合,造成错误,所以将映射关系改为:\(\large{[t[p].l,t[p].r] \Rightarrow [raw[t[p].l],raw[t[p].r+1]]}\) (左端点不变,右端点往前一格,调用函数时相应 \(-1\) 即可)
posted @ 2025-05-03 10:33  Hirasawayuiii  阅读(11)  评论(0)    收藏  举报