[线段树系列 #1] 扫描线
[线段树系列 #1] 扫描线
核心思路
将矩形分割为这样:
对于每条竖向边,将y轴上的区间作为数据结构维护对象,从小到大遍历每个x(即可理解为从左到右扫描一遍),即可将分割图形的行为抽象为简单的数据结构操作。
例题
以扫描线从上到下扫为例具体来说,是线段树存储当前线覆盖的长度(横向),再乘上当前线与上条线
将读入的矩形的竖向边存起来并离散化
通过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);
}
易错点
- 线段树上节点对应区间与线段进行映射对应时,由于两条相邻线段的端点有可能重合,造成错误,所以将映射关系改为:\(\large{[t[p].l,t[p].r] \Rightarrow [raw[t[p].l],raw[t[p].r+1]]}\) (左端点不变,右端点往前一格,调用函数时相应 \(-1\) 即可)

浙公网安备 33010602011771号