package copypasta import ( "math/bits" "slices" "sort" ) /* 矩形面积并(离散化+扫描线) 讲解 https://leetcode.cn/problems/rectangle-area-ii/solutions/3078272/lazy-xian-duan-shu-sao-miao-xian-pythonj-4tkr/ 模板题 LC850 https://leetcode.cn/problems/rectangle-area-ii/ 模板题 https://www.luogu.com.cn/problem/P5490 LC3454 https://leetcode.cn/problems/separate-squares-ii/ https://codeforces.com/problemset/problem/258/E 2400 转化 https://ac.nowcoder.com/acm/contest/66651/C */ // 线段树每个节点维护一段横坐标区间 [lx, rx] type segRect []struct { l, r int minCoverLen int // 区间内被矩形覆盖次数最少的底边长之和 minCover int // 区间内被矩形覆盖的最小次数 todo int // 子树内的所有节点的 minCover 需要增加的量,注意这可能是负数 } // 根据左右儿子的信息,更新当前节点的信息 func (t segRect) maintain(o int) { lo, ro := &t[o<<1], &t[o<<1|1] mn := min(lo.minCover, ro.minCover) t[o].minCover = mn t[o].minCoverLen = 0 if lo.minCover == mn { // 只统计等于 minCover 的底边长之和 t[o].minCoverLen = lo.minCoverLen } if ro.minCover == mn { t[o].minCoverLen += ro.minCoverLen } } // 仅更新节点信息,不下传懒标记 func (t segRect) do(o, v int) { t[o].minCover += v t[o].todo += v } // 下传懒标记 func (t segRect) spread(o int) { v := t[o].todo if v != 0 { t.do(o<<1, v) t.do(o<<1|1, v) t[o].todo = 0 } } // 建树 func (t segRect) build(xs []int, o, l, r int) { t[o].l, t[o].r = l, r if l == r { t[o].minCoverLen = xs[l+1] - xs[l] return } m := (l + r) >> 1 t.build(xs, o<<1, l, m) t.build(xs, o<<1|1, m+1, r) t.maintain(o) } // 区间更新 func (t segRect) update(o, l, r, v int) { if l <= t[o].l && t[o].r <= r { t.do(o, v) return } t.spread(o) m := (t[o].l + t[o].r) >> 1 if l <= m { t.update(o<<1, l, r, v) } if m < r { t.update(o<<1|1, l, r, v) } t.maintain(o) } // 输入格式 (x1,y1,x2,y2),分别表示矩形的左下角和右上角(平面直角坐标系) // 时间复杂度 O(nlogn),与值域无关 func rectangleArea(rectangles [][]int) (ans int) { xs := make([]int, 0, len(rectangles)*2) type event struct{ y, lx, rx, delta int } events := make([]event, 0, len(rectangles)*2) for _, rect := range rectangles { lx, ly, rx, ry := rect[0], rect[1], rect[2], rect[3] xs = append(xs, lx, rx) events = append(events, event{ly, lx, rx, 1}, event{ry, lx, rx, -1}) } slices.Sort(xs) xs = slices.Compact(xs) // 没有矩形,或者矩形都是一条线 if len(xs) <= 1 { return } // 初始化线段树 n := len(xs) - 1 // len(xs) 个横坐标有 len(xs)-1 个差值 t := make(segRect, 2<<bits.Len(uint(n-1))) t.build(xs, 1, 0, n-1) // 模拟扫描线从下往上移动 slices.SortFunc(events, func(a, b event) int { return a.y - b.y }) for i, e := range events[:len(events)-1] { l := sort.SearchInts(xs, e.lx) // 离散化 r := sort.SearchInts(xs, e.rx) - 1 // 注意 r 对应着 xs[r] 与 xs[r+1]=e.rx 的差值 t.update(1, l, r, e.delta) // 更新被 [e.lx, e.rx] 覆盖的次数 sumLen := xs[len(xs)-1] - xs[0] // 总的底边长度 if t[1].minCover == 0 { // 需要去掉没被矩形覆盖的长度 sumLen -= t[1].minCoverLen } ans += sumLen * (events[i+1].y - e.y) // 新增面积 = 被至少一个矩形覆盖的底边长之和 * 矩形高度 } return } // 横纵坐标范围均在 [0,n-1] 中,获取每条水平扫描线经过多少个在矩形中(包括边界)的整点 // https://codeforces.com/problemset/problem/258/E 2400 转化 func lineCover(n int, rectangles [][]int) []int { type event struct{ lx, rx, delta int } events := make([][]event, n+1) for _, rect := range rectangles { lx, ly, rx, ry := rect[0], rect[1], rect[2], rect[3] events[ly] = append(events[ly], event{lx, rx, 1}) events[ry+1] = append(events[ry+1], event{lx, rx, -1}) } // 初始化线段树 xs := make([]int, n+1) for i := range xs { xs[i] = i } t := make(segRect, 2<<bits.Len(uint(n-1))) t.build(xs, 1, 0, n-1) // 本意上,要把 minCoverLen 都置为 1 ans := make([]int, n) // 模拟扫描线从下往上移动 for i, es := range events[:n] { for _, e := range es { // 注意这里不是 e.rx-1,因为我们算的是点的个数,不是边长 t.update(1, e.lx, e.rx, e.delta) // 更新被 [e.lx, e.rx] 覆盖的次数 } ans[i] = n // 总的底边长度 if t[1].minCover == 0 { // 需要去掉没被矩形覆盖的长度 ans[i] -= t[1].minCoverLen } } return ans }