Skip to content

Commit 061a41a

Browse files
author
liaomingtian
committed
✨ feat(LiChaoTree):
1 parent cd7d8e5 commit 061a41a

20 files changed

+1171
-25
lines changed
+338
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"math/bits"
7+
"os"
8+
"sort"
9+
)
10+
11+
func main() {
12+
SegmentAddGetMin()
13+
}
14+
15+
// https://judge.yosupo.jp/problem/line_add_get_min
16+
// 0 a b: add line y = ax + b
17+
// 1 x: query min y
18+
func LineAddGetMin() {
19+
in, out := bufio.NewReader(os.Stdin), bufio.NewWriter(os.Stdout)
20+
defer out.Flush()
21+
22+
var n, q int
23+
fmt.Fscan(in, &n, &q)
24+
25+
operations := make([][3]int, 0, q+n)
26+
queryX := []int{}
27+
for i := 0; i < n; i++ {
28+
var a, b int
29+
fmt.Fscan(in, &a, &b)
30+
operations = append(operations, [3]int{0, a, b})
31+
}
32+
for i := 0; i < q; i++ {
33+
var op int
34+
fmt.Fscan(in, &op)
35+
if op == 0 {
36+
var a, b int
37+
fmt.Fscan(in, &a, &b)
38+
operations = append(operations, [3]int{0, a, b})
39+
} else {
40+
var x int
41+
fmt.Fscan(in, &x)
42+
operations = append(operations, [3]int{1, x, 0})
43+
queryX = append(queryX, x)
44+
}
45+
}
46+
47+
lichao := NewLiChaoTreeCompress(queryX, true)
48+
for _, op := range operations {
49+
if op[0] == 0 {
50+
lichao.AddLine(Line{k: op[1], b: op[2]})
51+
} else {
52+
fmt.Fprintln(out, lichao.Query(op[1]).value)
53+
}
54+
}
55+
}
56+
57+
// https://judge.yosupo.jp/problem/segment_add_get_min
58+
func SegmentAddGetMin() {
59+
in, out := bufio.NewReader(os.Stdin), bufio.NewWriter(os.Stdout)
60+
defer out.Flush()
61+
62+
var n, q int
63+
fmt.Fscan(in, &n, &q)
64+
65+
operations := make([][5]int, 0, q+n)
66+
queryX := []int{}
67+
for i := 0; i < n; i++ {
68+
var startX, endX, a, b int
69+
fmt.Fscan(in, &startX, &endX, &a, &b)
70+
operations = append(operations, [5]int{0, a, b, startX, endX})
71+
}
72+
for i := 0; i < q; i++ {
73+
var op int
74+
fmt.Fscan(in, &op)
75+
if op == 0 {
76+
var startX, endX, a, b int
77+
fmt.Fscan(in, &startX, &endX, &a, &b)
78+
operations = append(operations, [5]int{0, a, b, startX, endX})
79+
} else {
80+
var x int
81+
fmt.Fscan(in, &x)
82+
operations = append(operations, [5]int{1, x, 0, 0, 0})
83+
queryX = append(queryX, x)
84+
}
85+
}
86+
87+
lichao := NewLiChaoTreeCompress(queryX, true)
88+
for _, op := range operations {
89+
if op[0] == 0 {
90+
k, b, startX, endX := op[1], op[2], op[3], op[4]
91+
lichao.AddSegment(startX, endX, Line{k: k, b: b})
92+
} else {
93+
res := lichao.Query(op[1])
94+
if res.lineId == -1 {
95+
fmt.Fprintln(out, "INFINITY")
96+
} else {
97+
fmt.Fprintln(out, res.value)
98+
}
99+
}
100+
}
101+
}
102+
103+
func demo() {
104+
tree := NewLiChaoTreeCompress([]int{0, 1, 2, 3, 4, 5, 6, 99999999999}, true)
105+
tree.AddLine(Line{k: 1, b: 0})
106+
tree.AddLine(Line{k: 2, b: 0})
107+
fmt.Println(tree.Query(6))
108+
tree = NewLiChaoTreeNoCompress(0, 100, true)
109+
tree.AddSegment(0, 6, Line{k: 1, b: 0})
110+
tree.AddSegment(0, 100, Line{k: 2, b: 0})
111+
fmt.Println(tree.Query(6))
112+
}
113+
114+
type T = int
115+
116+
const INF T = 1e18
117+
118+
type Line struct{ k, b T } // y = k * x + b
119+
120+
// Evaluate を書き変えると、totally monotone な関数群にも使える
121+
func Evaluate(line Line, x int) T {
122+
return line.k*x + line.b
123+
}
124+
125+
type queryPair = struct {
126+
value T
127+
lineId int
128+
}
129+
130+
type LiChaoTree struct {
131+
n, offset int
132+
lower, higher int
133+
compress bool
134+
minimize bool
135+
xs []int
136+
lines []Line
137+
lineIds []int
138+
}
139+
140+
// 指定查询的 x 值建立李超线段树,采用坐标压缩.
141+
func NewLiChaoTreeCompress(queryX []int, minimize bool) *LiChaoTree {
142+
set := make(map[int]struct{})
143+
for _, x := range queryX {
144+
set[x] = struct{}{}
145+
}
146+
unique := make([]int, 0, len(set))
147+
for x := range set {
148+
unique = append(unique, x)
149+
}
150+
sort.Ints(unique)
151+
n := len(unique)
152+
log := 1
153+
for (1 << log) < n {
154+
log++
155+
}
156+
offset := 1 << log
157+
lineIds := make([]int, offset<<1)
158+
for i := range lineIds {
159+
lineIds[i] = -1
160+
}
161+
return &LiChaoTree{
162+
n: n, offset: offset,
163+
compress: true, minimize: minimize,
164+
xs: unique,
165+
lineIds: lineIds,
166+
}
167+
}
168+
169+
// 指定查询的 x 值范围建立李超线段树,不采用坐标压缩.
170+
// higher - lower <= 1e6.
171+
func NewLiChaoTreeNoCompress(lower, higher int, minimize bool) *LiChaoTree {
172+
n := higher - lower
173+
log := 1
174+
for (1 << log) < n {
175+
log++
176+
}
177+
offset := 1 << log
178+
lineIds := make([]int, offset<<1)
179+
for i := range lineIds {
180+
lineIds[i] = -1
181+
}
182+
return &LiChaoTree{
183+
n: n, offset: offset,
184+
lower: lower, higher: higher,
185+
compress: false, minimize: minimize,
186+
lineIds: lineIds,
187+
}
188+
}
189+
190+
// O(logn)
191+
func (tree *LiChaoTree) AddLine(line Line) {
192+
id := len(tree.lines)
193+
tree.lines = append(tree.lines, line)
194+
tree._addLineAt(1, id)
195+
}
196+
197+
// [start, end)
198+
// O(log^2n)
199+
func (tree *LiChaoTree) AddSegment(startX, endX int, line Line) {
200+
if startX >= endX {
201+
return
202+
}
203+
id := len(tree.lines)
204+
tree.lines = append(tree.lines, line)
205+
startX = tree._getIndex(startX) + tree.offset
206+
endX = tree._getIndex(endX) + tree.offset
207+
for startX < endX {
208+
if startX&1 == 1 {
209+
tree._addLineAt(startX, id)
210+
startX++
211+
}
212+
if endX&1 == 1 {
213+
endX--
214+
tree._addLineAt(endX, id)
215+
}
216+
startX >>= 1
217+
endX >>= 1
218+
}
219+
}
220+
221+
// O(logn)
222+
func (tree *LiChaoTree) Query(x int) queryPair {
223+
x = tree._getIndex(x)
224+
pos := x + tree.offset
225+
res := queryPair{lineId: -1}
226+
if tree.minimize {
227+
res.value = INF
228+
} else {
229+
res.value = -INF
230+
}
231+
232+
for pos > 0 {
233+
if id := tree.lineIds[pos]; id != -1 && id != res.lineId {
234+
cand := queryPair{value: tree._evaluateInner(id, x), lineId: id}
235+
if tree.minimize {
236+
if cand.value < res.value {
237+
res = cand
238+
}
239+
} else {
240+
if cand.value > res.value {
241+
res = cand
242+
}
243+
}
244+
}
245+
pos >>= 1
246+
}
247+
return res
248+
}
249+
250+
func (tree *LiChaoTree) _addLineAt(i, fid int) {
251+
upperBit := 31 - bits.LeadingZeros32(uint32(i))
252+
left := (tree.offset >> upperBit) * (i - (1 << upperBit))
253+
right := left + (tree.offset >> upperBit)
254+
minimize := tree.minimize
255+
for left < right {
256+
gid := tree.lineIds[i]
257+
fl := tree._evaluateInner(fid, left)
258+
fr := tree._evaluateInner(fid, right-1)
259+
gl := tree._evaluateInner(gid, left)
260+
gr := tree._evaluateInner(gid, right-1)
261+
var bl, br bool
262+
if minimize {
263+
bl = fl < gl
264+
br = fr < gr
265+
} else {
266+
bl = fl > gl
267+
br = fr > gr
268+
}
269+
if bl && br {
270+
tree.lineIds[i] = fid
271+
return
272+
}
273+
if !bl && !br {
274+
return
275+
}
276+
mid := (left + right) >> 1
277+
fm := tree._evaluateInner(fid, mid)
278+
gm := tree._evaluateInner(gid, mid)
279+
var bm bool
280+
if minimize {
281+
bm = fm < gm
282+
} else {
283+
bm = fm > gm
284+
}
285+
if bm {
286+
tree.lineIds[i] = fid
287+
fid = gid
288+
if !bl {
289+
i <<= 1
290+
right = mid
291+
} else {
292+
i = i<<1 | 1
293+
left = mid
294+
}
295+
} else {
296+
if bl {
297+
i <<= 1
298+
right = mid
299+
} else {
300+
i = i<<1 | 1
301+
left = mid
302+
}
303+
}
304+
}
305+
}
306+
307+
func (tree *LiChaoTree) _evaluateInner(fid int, x int) T {
308+
if fid == -1 {
309+
if tree.minimize {
310+
return INF
311+
}
312+
return -INF
313+
}
314+
var target int
315+
if tree.compress {
316+
target = tree.xs[min(x, tree.n-1)]
317+
} else {
318+
target = x + tree.lower
319+
}
320+
return Evaluate(tree.lines[fid], target)
321+
}
322+
323+
func (tree *LiChaoTree) _getIndex(x int) int {
324+
if tree.compress {
325+
return sort.SearchInts(tree.xs, x)
326+
}
327+
if x < tree.lower || x > tree.higher {
328+
panic("x out of range")
329+
}
330+
return x - tree.lower
331+
}
332+
333+
func min(a, b int) int {
334+
if a <= b {
335+
return a
336+
}
337+
return b
338+
}

0 commit comments

Comments
 (0)