-
Notifications
You must be signed in to change notification settings - Fork 653
/
Copy pathsegment_tree_rect.go
157 lines (142 loc) · 4.51 KB
/
segment_tree_rect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
}