yohandi
A recent graduate with a strong interest in algorithms and data structures.

[Tutorial] DP Optimization: Convex Hull Trick

"Geometry being useful", I copied that down from this blog. And yes, in any subject, geometry often steps in to offer a fresh perspective. The Convex Hull Trick is a testament to this, seamlessly blending algorithmic efficiency with geometric intuition. In this post, I will discuss pretty much about this method.

Motivational Problem

Suppose there are

KaTeX can only parse string typed expression
rectangles in a 2D Cartesian Plane, each defined by vertices
KaTeX can only parse string typed expression
and
KaTeX can only parse string typed expression
, along with a number
KaTeX can only parse string typed expression
. We want to select some of these rectangles such that the area of their union minus the sum of the
KaTeX can only parse string typed expression
values of the chosen rectangles is maximized.

Constraints given:

  • KaTeX can only parse string typed expression
  • KaTeX can only parse string typed expression
    (for each
    KaTeX can only parse string typed expression
    such that
    KaTeX can only parse string typed expression
    )
  • KaTeX can only parse string typed expression
    (for each
    KaTeX can only parse string typed expression
    such that
    KaTeX can only parse string typed expression
    )
  • No nested rectangles exist

This problem comes straight from Codeforces Round 526 (Div. 1E).

Dynamic Programming Solution

Let's consider these

KaTeX can only parse string typed expression
tuples:
KaTeX can only parse string typed expression
.

Because no nested rectangles exist in all cases, we can sort these tuples by

KaTeX can only parse string typed expression
without losing their essences. Note that sorting by
KaTeX can only parse string typed expression
in ascending order also sorts the tuples by
KaTeX can only parse string typed expression
in descending order.

Denote

KaTeX can only parse string typed expression
as the maximum value that can be achieved by selecting the
KaTeX can only parse string typed expression
-th rectangle (from the sorted version) and some of the previous rectangles. Then, our answer will be

KaTeX can only parse string typed expression

, where

KaTeX can only parse string typed expression

.

Parts explanation:

  • The
    KaTeX can only parse string typed expression
    part is obtained as the reward for selecting the
    KaTeX can only parse string typed expression
    -th rectangle.
  • The
    KaTeX can only parse string typed expression
    part acts as an additional reward for selecting the
    KaTeX can only parse string typed expression
    -th rectangle.
  • The
    KaTeX can only parse string typed expression
    part, which is derived from
    KaTeX can only parse string typed expression
    , is obtained as the consequence of selecting the
    KaTeX can only parse string typed expression
    -th rectangle with
    KaTeX can only parse string typed expression
    -th rectangle (and presumably any rectangle that maximizes the
    KaTeX can only parse string typed expression
    value). Note that
    KaTeX can only parse string typed expression
    is the intersection for the
    KaTeX can only parse string typed expression
    -th rectangle and the
    KaTeX can only parse string typed expression
    -rectangle. This part is important as the union area of the
    KaTeX can only parse string typed expression
    -th rectangle and the
    KaTeX can only parse string typed expression
    -th rectangle is
    KaTeX can only parse string typed expression
    .

This dynamic programming solution works in

KaTeX can only parse string typed expression
; however, it is still not suffice as
KaTeX can only parse string typed expression
might be
KaTeX can only parse string typed expression
. We need an optimization towards it.

Convex Hull Trick

This is where our main optimization technique, the Convex Hull Trick, comes in handy. The Convex Hull Trick is a computational geometry used to manage a set of affine functions, constructing a lower / upper hull. To illustrate, suppose we are going to insert the following affine functions:

KaTeX can only parse string typed expression
,
KaTeX can only parse string typed expression
,
KaTeX can only parse string typed expression
, and
KaTeX can only parse string typed expression
, into our lower hull.

Notice that in our lower hull, we always want to have the intersection points of every two lines that are side by side sorted. Formally, if we have a set of affine functions in our lower hull, say

KaTeX can only parse string typed expression
,
KaTeX can only parse string typed expression
,
KaTeX can only parse string typed expression
, and
KaTeX can only parse string typed expression
, with
KaTeX can only parse string typed expression
representing the x-coordinate of the intersection point of
KaTeX can only parse string typed expression
and
KaTeX can only parse string typed expression
, i.e.,
KaTeX can only parse string typed expression
, for
KaTeX can only parse string typed expression
; then, it is essential that
KaTeX can only parse string typed expression
(or sorted in the other way).

If there is a scenario where

KaTeX can only parse string typed expression
, we can discard either
KaTeX can only parse string typed expression
or
KaTeX can only parse string typed expression
. The choice depends on the gradient, considering whether it is lower / higher and negative / positive.

The implementation is as follows:

struct Line {
  long long m, c;
  Line(long long m, long long c) : m(m), c(c) {}

  long long y(long long x) { return m * x + c; }
  double intersect(Line other) const {
    return (double)(c - other.c) / (double)(other.m - m);
  }
};

vector<Line> ch;

void addLine(Line line) {
  auto comp = [&](const Line &a, const Line &b, const Line &c) -> bool {
    return a.intersect(b) > b.intersect(c); // p_0 > p_1 > ... > p_{n - 2}
  };

  while (ch.size() >= 2 && !comp(ch[ch.size() - 2], ch.back(), line)) {
    ch.pop_back();
  }

  ch.push_back(line);
}

The implementation above operates efficiently when the inserted gradient is in descending order. The reason will be explained later.

In our set of affine functions, it can be observed that:

  • KaTeX can only parse string typed expression
    maximizes the value of
    KaTeX can only parse string typed expression
    for
    KaTeX can only parse string typed expression
    ;
  • KaTeX can only parse string typed expression
    maximizes the value of
    KaTeX can only parse string typed expression
    for
    KaTeX can only parse string typed expression
    ;
  • KaTeX can only parse string typed expression
  • KaTeX can only parse string typed expression
    maximizes the value of
    KaTeX can only parse string typed expression
    for
    KaTeX can only parse string typed expression
    ;
  • KaTeX can only parse string typed expression
    maximizes the value of
    KaTeX can only parse string typed expression
    for
    KaTeX can only parse string typed expression
    .

Using this observation, whenever we want to look up for

KaTeX can only parse string typed expression
that maximizes
KaTeX can only parse string typed expression
for an
KaTeX can only parse string typed expression
, we can perform a binary search on the intersection points to determine the domain where
KaTeX can only parse string typed expression
lies.

int optimalLine(long long x) {
  auto p = [&](const int i) -> double { return ch[i].intersect(ch[i + 1]); };

  return lower_bound(ch.begin(), ch.end() - 1, x,
                     [&](const Line &line, long long val) {
                       int idx = &line - &ch[0];
                       return p(idx) > val;
                     }) -
         ch.begin();
}

Reverting to our primary dynamic programming solution, where we had:

KaTeX can only parse string typed expression

Regardless of the value of

KaTeX can only parse string typed expression
, we consistently add
KaTeX can only parse string typed expression
to
KaTeX can only parse string typed expression
; thus, this can be omitted from our optimization process. An affine function
KaTeX can only parse string typed expression
is formed by assigning
KaTeX can only parse string typed expression
as
KaTeX can only parse string typed expression
and
KaTeX can only parse string typed expression
as
KaTeX can only parse string typed expression
. If
KaTeX can only parse string typed expression
represents a constant in
KaTeX can only parse string typed expression
, then
KaTeX can only parse string typed expression
. Our
KaTeX can only parse string typed expression
equation can then be expressed as:

KaTeX can only parse string typed expression

To further simplify our dynamic programming, we may also add a line

KaTeX can only parse string typed expression
to handle the
KaTeX can only parse string typed expression
component.

KaTeX can only parse string typed expression

The solution's flow is as follows:

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);

  struct InputFormat {
    long long x, y, A;
    bool operator<(const InputFormat &other) const {
      return tie(x, y, A) < tie(other.x, other.y, other.A);
    }
  };

  long long N, ans = 0;
  cin >> N;

  vector<InputFormat> arr(N);
  vector<long long> x(N), y(N), A(N), dp(N);

  for (auto &a : arr)
    cin >> a.x >> a.y >> a.A;
  sort(arr.begin(), arr.end());

  for (int i = 0; i < N; ++i)
    tie(x[i], y[i], A[i]) = make_tuple(arr[i].x, arr[i].y, arr[i].A);

  addLine(Line(0, 0));
  for (int i = 0; i < N; ++i) {
    dp[i] = ch[optimalLine(y[i])].y(y[i]) + (x[i] * y[i] - A[i]);
    addLine(Line(-x[i], dp[i]));

    ans = max(ans, dp[i]);
  }

  cout << ans << endl;
  return 0;
}

The gradient of the inserted line is guaranteed to be in descending order as the gradient is

KaTeX can only parse string typed expression
and
KaTeX can only parse string typed expression
is in ascending order. This explains the previous
KaTeX can only parse string typed expression
implementation. Querying the
KaTeX can only parse string typed expression
only performs binary search from
KaTeX can only parse string typed expression
to
KaTeX can only parse string typed expression
, resulting in a complexity of
KaTeX can only parse string typed expression
per query. The armortized total operations in
KaTeX can only parse string typed expression
function is
KaTeX can only parse string typed expression
. The overall solution operates in
KaTeX can only parse string typed expression
. My full solution is accessible here.

Variation

Upper Hull

In a case where we have a minimization problem, we want to look up for the upper hull. Some comparisons in the code, especially in

KaTeX can only parse string typed expression
within
KaTeX can only parse string typed expression
and the binary search in
KaTeX can only parse string typed expression
, need to be inverted.

Dynamic Convex Hull Trick

In this particular case, we do not have the guarantee of having the gradient insertion sorted, whether it is in ascending order or in descending order; consequently, we no longer can use

KaTeX can only parse string typed expression
for our data structure. We may use
KaTeX can only parse string typed expression
to maintain the dynamic gradients. Or... we may use Li Chao Tree, which I will discuss in the upcoming post.

And there we have it! I have always found the Convex Hull Trick fascinating, especially when it comes to refine a naive

KaTeX can only parse string typed expression
dynamic programming approach down to
KaTeX can only parse string typed expression
. Of course, it still requires an additional property, which is to have the dynamic programming rewritable as an affine function. Some other DP optimizations, such as Divide and Conquer, Knuth, and more will probably discussed in the future posts as well! Stay tuned!

© 2023 Yohandi. All rights reserved.