加载中...

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);
}

code

posted @ 2026-03-20 14:46  小橘奏  阅读(19)  评论(0)    收藏  举报