Skip to content

Commit 794515c

Browse files
committed
Space optimization
1 parent 68c0087 commit 794515c

File tree

2 files changed

+34
-25
lines changed

2 files changed

+34
-25
lines changed

src/li_chao.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
/// A structure for answering minimum queries on a set of linear functions. Supports two
2-
/// operations: inserting a linear function and querying for minimum at a given point. Unlike the
1+
/// A structure for answering maximum queries on a set of linear functions. Supports two
2+
/// operations: inserting a linear function and querying for maximum at a given point. Unlike the
33
/// simplest convex hull trick implementation, the queries can be done in any order, and we can do
44
/// all the calculations using integers.
5+
/// https://cp-algorithms.com/geometry/convex_hull_trick.html#li-chao-tree
6+
/// Compared to the code in the above link, this implementation further improves the algorithm by
7+
/// reducing the number of nodes to (right - left). This is done by removing the midpoint of a
8+
/// segment from both children. Even better, this allows the index of a node to just be the
9+
/// midpoint of the interval!
510
6-
/// This data structure builds a static segment tree over the interval of x-coordinates
7-
/// [left,right), so requires O(right-left) memory. Just like normal segment trees, this could be
8-
/// modified to a dynamic tree when the range is huge (it can also be made persistent!).
9-
/// It can also probably be done as an AlCash style iterative segment tree in the static case.
11+
/// Just like normal segment trees, this could be modified to a dynamic tree when the range is huge
12+
/// (it can also be made persistent!).
1013
1114
pub struct LiChaoTree {
1215
left: i64,
@@ -20,54 +23,60 @@ impl LiChaoTree {
2023
Self {
2124
left,
2225
right,
23-
lines: vec![(0, i64::MIN); 4 * (right - left + 1) as usize],
26+
lines: vec![(0, std::i64::MIN); (right - left) as usize],
2427
}
2528
}
2629

27-
/// Every node in the tree has the property that the line that minimizes its midpoint is found
30+
/// Every node in the tree has the property that the line that maximizes its midpoint is found
2831
/// either in the node or one of its ancestors. When we visit a node, we compute the winner at
2932
/// the midpoint of the node. The winner is stored in the node. The loser can still possibly
3033
/// beat the winner on some segment, either to the left or to the right of the current
3134
/// midpoint, so we propagate it to that segment. This sequence ensures that the invariant is
3235
/// kept.
33-
fn add_line_impl(&mut self, mut m: i64, mut b: i64, ix: usize, l: i64, r: i64) {
34-
let x = (l + r) / 2;
36+
fn add_line_impl(&mut self, mut m: i64, mut b: i64, l: i64, r: i64) {
37+
if r <= l {
38+
return;
39+
}
40+
let ix = ((r - self.left + l - self.left) / 2) as usize;
41+
let x = self.left + (ix as i64);
3542
let (ref mut m_ix, ref mut b_ix) = self.lines[ix];
3643
if m * x + b > *m_ix * x + *b_ix {
3744
std::mem::swap(&mut m, m_ix);
3845
std::mem::swap(&mut b, b_ix);
3946
}
40-
if r - l > 1 {
41-
if m < self.lines[ix].0 {
42-
self.add_line_impl(m, b, 2 * ix, l, x);
43-
} else {
44-
self.add_line_impl(m, b, 2 * ix + 1, x, r);
45-
}
47+
if m < self.lines[ix].0 {
48+
self.add_line_impl(m, b, l, x);
49+
} else {
50+
self.add_line_impl(m, b, x + 1, r);
4651
}
4752
}
4853

4954
/// Adds the line with slope m and intercept b. O(log N) complexity.
5055
pub fn add_line(&mut self, m: i64, b: i64) {
51-
self.add_line_impl(m, b, 1, self.left, self.right);
56+
self.add_line_impl(m, b, self.left, self.right);
5257
}
5358

5459
/// Because of the invariant established by add_line, we know that the best line for a given
55-
/// point is stored in one of the ancestors of its node. So we accumulate the minimum answer as
60+
/// point is stored in one of the ancestors of its node. So we accumulate the maximum answer as
5661
/// we go back up the tree.
57-
fn query_impl(&self, x: i64, ix: usize, l: i64, r: i64) -> i64 {
62+
fn query_impl(&self, x: i64, l: i64, r: i64) -> i64 {
63+
if r == l {
64+
return std::i64::MIN;
65+
}
66+
let ix = ((r - self.left + l - self.left) / 2) as usize;
5867
let y = self.lines[ix].0 * x + self.lines[ix].1;
5968
if r - l == 1 {
6069
y
6170
} else if x < (l + r) / 2 {
62-
self.query_impl(x, 2 * ix, l, (l + r) / 2).max(y)
71+
self.query_impl(x, l, self.left + ix as i64).max(y)
6372
} else {
64-
self.query_impl(x, 2 * ix + 1, (l + r) / 2, r).max(y)
73+
self.query_impl(x, self.left + 1 + ix as i64, r).max(y)
6574
}
6675
}
6776

68-
/// Finds the minimum mx+b among all lines in the structure. O(log N) complexity.
77+
/// Finds the maximum mx+b among all lines in the structure. O(log N) complexity.
6978
pub fn query(&self, x: i64) -> i64 {
70-
self.query_impl(x, 1, self.left, self.right)
79+
self.query_impl(x, self.left, self.right)
7180
}
7281
}
7382

@@ -90,7 +99,7 @@ mod test {
9099
];
91100
let mut li_chao = LiChaoTree::new(0, 6);
92101

93-
assert_eq!(li_chao.query(0), i64::MIN);
102+
assert_eq!(li_chao.query(0), std::i64::MIN);
94103
for (&(slope, intercept), expected) in lines.iter().zip(results.iter()) {
95104
li_chao.add_line(slope, intercept);
96105
let ys: Vec<i64> = xs.iter().map(|&x| li_chao.query(x)).collect();

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! Algorithms Cookbook in Rust.
22
pub mod graph;
3+
pub mod li_chao;
34
pub mod math;
45
pub mod misc;
56
pub mod range_query;
67
pub mod scanner;
78
pub mod string_proc;
8-
pub mod li_chao;

0 commit comments

Comments
 (0)