ABC 449 F (扫描线+线段树 维护大范围矩形面积并)
F - Grid Clipping
求大范围矩形面积并的模板题,这里脱离题意,只记录矩形面积并是如何利用扫描线+线段树维护的。
大致情景:在 \(1\le x\le 10^{9}, 1\le y\le 10^{9}\) 范围内,存在 \(n\le 10^{5}\) 个矩形,求它们的面积并。
由于 \(x,y\) 的范围都非常大,显然需要考虑离散化,而矩形的数量是很少的,有用的端点数量也很少。于是对于每个矩形的 \(([xl, xr],[yl, yr])\),先离散化(注意离散化之前先做预处理:将所有右端点加 1,并将所有区间转化成左闭右开的形式,这样做更容易用点表示区间,使得每个点表示的是一个连续段,也就是以自身为左端点,排序后的下一个点为右端点的左闭右开区间)
然后考虑枚举每个 \(x\)(也就是每个水平子段,也是左闭右开形式),线段树维护 \(y\) 在该段内覆盖的总长度。具体维护方式:对于线段树上的每个结点,维护 覆盖该子区间的区间数量 \(cnt\) 与 该区间被覆盖的总长度 \(len\)。每次添加或删除某个区间时,根据线段树自身的性质,该区间一定对应线段树中的 \(O(\log)\) 个区间。我们不需要因为涉及区间加减添加任何懒标记,只需要在相应的 \(O(\log)\) 个子区间上作修改即可。具体地:
struct node {
int cnt, len;
}seg[N << 2];
map<int, int> rec;
void modify(int p, int l, int r, int ql, int qr, int val) {
if (ql > qr) return;
if (ql <= l && r <= qr) {
seg[p].cnt += val;
if(seg[p].cnt > 0) seg[p].len = rec[r+1] - rec[l]; // 被至少一个区间完全覆盖,长度可以直接求
else if(l == r) seg[p].len = 0; // 叶子结点不表示任何子段,值一定是0
else seg[p].len = seg[lc].len + seg[rc].len; // 没有被任何区间完全覆盖,答案取两个子区间的答案之和
return;
}
int mid = l + r >> 1;
if (ql <= mid)
modify(lc, l, r, ql, qr, val);
if (qr > mid)
modify(rc, l, r, ql, qr, val);
// pushup
if(seg[p].cnt > 0) seg[p].len = rec[r+1] - rec[l];
else if(l == r) seg[p].len = 0;
else seg[p].len = seg[lc].len + seg[rc].len;
}
主调用函数部分:
int lst = 0;
ll sum = 0;
for(auto [x, ly, ry, val] : upd){ // x 表示区间左/右端点;val 为 1/-1,表示区间的增加/删除
sum += (x - lst) * seg[1].len;
ly = mp[ly];
ry = mp[ry];
modify(1, 1, tot, ly, ry - 1, val); // 左闭右开
lst = x;
}
离散化处理部分:
vector<array<int,4>> upd;
vector<int> vec_y;
vector<int> alls_y;
for(int i = 1; i <= n; i++){
int xl, xr, yl, yr;
// x,y 方向上的区间均左闭右开化
upd.push_back({xl, yl, yr + 1, 1});
upd.push_back({xr + 1, yl, yr + 1, -1});
alls_y.push_back(yl);
alls_y.push_back(yr + 1);
}

浙公网安备 33010602011771号