The convex hull trick is a technique (perhaps best classified as a data structure) used to determine efficiently, after preprocessing, which member of a set of linear functions in one variable attains an extremal value for a given value of the independent variable. It has little to do with convex hull algorithms.
Problem : Covered Walkway We need to cover a walkway consisting of n(1 ≤ n ≤ 10^6
) points. To cover the walkway from a point at x
to a point at y
, they will charge c + (x − y)²
, where c
is a constant. Note that it is possible for x = y
. If so, then the contractor would simply charge c
.
Solution :
n
points on the plane with some number of segments.(l,r)
is c + (r − l)²
.O(N²)
and is too slow.yi(x) = ai x + bi
add(ai, bi)
and get(x) = mini yi(x)
.ai
.y = ai + b
. getX(a, b)
returns x
coordinate of intersection of lines a
and b
.
while( getX(prevLast, newLine) < getX(prevLast, last))
remove ( last )
x
: find an optimal line such that x
lies between intersection with neighboring linesO(log n)
.x
are always increasing, you can keep a pointer to the current best line.O(1)
amortized.#define LL long long
const LL is_query = -(1LL << 62);
struct Line {
LL m, b;
mutable function<const Line*()> succ;
bool operator<(const Line& rhs) const {
if (rhs.b != is_query) return m > rhs.m;
const Line* s = succ();
if (!s) return 0;
return s->b - b < (m - s->m) * rhs.m;
}
};
struct HullDynamic : public multiset<Line> {
bool bad(iterator y) { // maintains lower hull for min
auto z = next(y);
if (y == begin()) {
if (z == end()) return 0;
return y->m == z->m && y->b >= z->b;
}
auto x = prev(y);
if (z == end()) return y->m == x->m && y->b >= x->b;
return (x->b - y->b) * (z->m - y->m) >= (y->b - z->b) * (y->m - x->m);
}
void insert_line(LL m, LL b) {
auto y = insert({m, b});
y->succ = [=] { return next(y) == end() ? 0 : &*next(y); };
if (bad(y)) {
erase(y);
return;
}
while (next(y) != end() && bad(next(y))) erase(next(y));
while (y != begin() && bad(prev(y))) erase(prev(y));
}
LL eval(LL x) {
auto l = *lower_bound((Line){x, is_query});
return l.m * x + l.b;
}
};
int main() {
int N;
long long C;
scanf("%d %lld", &N, &C);
HullDynamic CHT;
vector<long long> dp(N+1);
// dp[j] = C + xj^2 + min(-2 * xi *xj + xi^2 + dp[i-1])
for(int i=1;i<=N;i++){
int x;
scanf("%d", &x);
if(i == 1){
dp[i] = C;
}else{
dp[i] = min(C + 1ll*x*x + CHT.eval(x), C + dp[i-1]);
}
CHT.insert_line(-2ll*x, 1ll*x*x + dp[i-1]); // insert values
}
printf("%lld\n", dp[N]);
return 0;
}
Since we go from left to right, x
is increasing, we can store them in stack.
struct convex_hull_trick {
vector<pair<long double,long double> > h;
double intersect(int i) {
return (h[i+1].second-h[i].second) / (h[i].first-h[i+1].first); }
void add(long double m, long double b) {
h.push_back(make_pair(m,b));
while (size(h) >= 3) {
int n = size(h);
if (intersect(n-3) < intersect(n-2)) break;
swap(h[n-2], h[n-1]);
h.pop_back(); } }
long double get_min(long double x) {
int lo = 0, hi = (int)size(h) - 2, res = -1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (intersect(mid) <= x) res = mid, lo = mid + 1;
else hi = mid - 1; }
return h[res+1].first * x + h[res+1].second; } };
int main() {
int N;
long long C;
scanf("%d %lld", &N, &C);
convex_hull_trick CHT;
vector<long long> dp(N+1);
// dp[j] = C + xj^2 + min(-2 * xi *xj + xi^2 + dp[i-1])
for(int i=1;i<=N;i++){
int x;
scanf("%d", &x);
if(i == 1){
dp[i] = C;
}else{
dp[i] = min(C + 1ll*x*x + (ll)CHT.get_min(x), C + dp[i-1]);
}
CHT.add(-2ll*x, 1ll*x*x + dp[i-1]); // insert values
}
printf("%lld\n", dp[N]);
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
#define MAXN 1000100
long long X[MAXN];
long long Y[MAXN];
typedef pair < long long, long long > pll;
void conv_insert(int ii, vector < pll > & C, pll x) {
/* Erase everything from the set that is dominated by this line. If this line
* overtakes you before you overtake your predecessor you'll never be at the
* top so you get dropped. */
while (C.size() - ii > 1) {
int i = C.size() - 2;
int j = C.size() - 1;
if ((C[j].second - C[i].second) / (C[i].first - C[j].first) <
(C[j].second - x.second) / (x.first - C[j].first)) {
C.resize(C.size() - 1);
} else break;
}
C.push_back(x);
}
long long eval(pll v, int x) {
return v.second + x * v.first;
}
int main() {
int N, K;
cin >> N >> K;
for (int i = 0; i < N; i++) cin >> X[i];
/* The basic idea is we can use dynamic programming to solve this problem to
* get cost(i) = min((X[i] - X[j-1])^2 + cost(j)) for j > i and cost(N) = 0.
* We split this up into:
* cost(i) = X[i]^2 + min(-2X[i]X[j] + (X[j-1]^2 + cost(j)))
* where the first term is the only thing that will vary with X[i], i.e., we
* have lines. So I just sweep from right to left and keep track of which
* line is cheapest by ordering lines by when they become the best option.
*/
int ii = 0;
vector < pll > C;
/* Connect to end. */
C.push_back(make_pair(-2 * X[N - 1], X[N - 1] * X[N - 1]));
for (int i = N - 1;; i--) {
/* Remove lines that are no longer of interest. To be efficient we erase
* from the front by just incrementing the head 'ii' index. */
while (C.size() - ii > 1 && eval(C[ii], X[i]) > eval(C[ii + 1], X[i])) {
ii++;
}
/* Compute v = cost(i) */
long long v = K + X[i] * X[i] + eval(C[ii], X[i]);
if (i == 0) {
cout << v << endl;
break;
}
/* Add this new entry to our line set. Since it has the largest slope so
* far it always belongs on the set somewhere. */
conv_insert(ii, C, make_pair(-2 * X[i - 1], v + X[i - 1] * X[i - 1]));
}
return 0;
}
#define fi first
#define se second
#define sz(x) (int)(x).size()
// Convex Hull Trick
typedef ll LT;
typedef pair<LT,LT> LP;
struct convex_hull_trick {
deque<LP> d;
LT get(LT x) {
while (sz(d) >= 2) {
LT a = d[0].fi*x + d[0].se;
LT b = d[1].fi*x + d[1].se;
if (a <= b) break; // get min
d.pop_front();
}
return d[0].fi*x + d[0].se;
}
void add(LP x) { // decreasing order of x.fi
while (sz(d) >= 2) {
LP y = d[sz(d) - 1];
LP z = d[sz(d) - 2];
if ((x.se-y.se)*(z.fi-y.fi) >= (y.fi-x.fi)*(y.se-z.se)) break; // both
d.pop_back();
}
d.push_back(x);
}
};
int main() {
int N;
long long C;
scanf("%d %lld", &N, &C);
convex_hull_trick CHT;
vector<long long> dp(N+1);
// dp[j] = C + xj^2 + min(-2 * xi *xj + xi^2 + dp[i-1])
for(int i=1;i<=N;i++){
int x;
scanf("%d", &x);
if(i == 1){
dp[i] = C;
}else{
dp[i] = min(C + 1ll*x*x + (ll)CHT.get(x), C + dp[i-1]);
}
CHT.add({-2ll*x, 1ll*x*x + dp[i-1]}); // insert values
}
printf("%lld\n", dp[N]);
return 0;
}
source: https://usaco.guide/plat/cht?lang=cpp
There are mainly three cases:
In the case where the slopes aren't necessarily sorted, so we have 5 options:
The last 2 have worse complexity compared to the first 3 and may get TLE depending on the implementation. REF
We call a line y
irrelevant/bad with line x
& z
if it is not part of minimum hull for any segment [l, r]
.
Here we no need to consider line y.
struct convex_hull_trick {
vector<pair<double,double> > h;
double intersect(int i) {
return (h[i+1].second-h[i].second) /
(h[i].first-h[i+1].first); }
void add(double m, double b) {
h.push_back(make_pair(m,b));
while (size(h) >= 3) {
int n = size(h);
if (intersect(n-3) < intersect(n-2)) break;
swap(h[n-2], h[n-1]);
h.pop_back(); } }
double get_min(double x) {
int lo = 0, hi = (int)size(h) - 2, res = -1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (intersect(mid) <= x) res = mid, lo = mid + 1;
else hi = mid - 1; }
return h[res+1].first * x + h[res+1].second; } };
A data structure which maintain a some lines in (m, b)
format where y = m*x + b
and we can query for the minimum value for a given x
. The following are mostly inspired from simonlindholm
KTH notebook and nikalsb, code
multiset<Line>
maintains the set of lines, ordered by slope. Now the problem is to search the matching line for a given X coordinate in eval
, we need to binary search on the intersection points of adjacent lines. Unfortunately multiset has no option to customize the binary search, so every line is given a std::function<const Line*()>
, so that it can look up it's successor while comparing. Then we use lower_bound
to do the binary search, using a special query line. Line::operator<
contains a hack so that it behaves differently when comparing against such a special query line.
bad
function: Given line y(m, b)
it intersects it's next line z(m, c)
their intesection has x-coordinate x * z->m + z->b = x * y->m + y->b
which leads to (y->b - z>b)/(z->m - y->m)
. Similarly the intersection of y(m, c)
and it's prev x(m, c)
has x-coordinate (x->b - y->b)/(y->m - x->m)
. If we cross-multiply to eliminate division we will end up with (x->b - y->b) * (z->m - y->m) >= (y->b - z->b) * (y->m - x->m);
. bad
returns true if either line prev(y)
or line next(y)
is always better than line y
.
Maintains lower hull for min
#define LL long long
const LL is_query = -(1LL << 62);
struct Line {
LL m, b;
mutable function<const Line*()> succ;
bool operator<(const Line& rhs) const {
if (rhs.b != is_query) return m > rhs.m;
const Line* s = succ();
if (!s) return 0;
return s->b - b < (m - s->m) * rhs.m;
}
};
struct HullDynamic : public multiset<Line> {
bool bad(iterator y) { // maintains lower hull for min
auto z = next(y);
if (y == begin()) {
if (z == end()) return 0;
return y->m == z->m && y->b >= z->b;
}
auto x = prev(y);
if (z == end()) return y->m == x->m && y->b >= x->b;
return (x->b - y->b) * (z->m - y->m) >= (y->b - z->b) * (y->m - x->m);
}
void insert_line(LL m, LL b) {
auto y = insert({m, b});
y->succ = [=] { return next(y) == end() ? 0 : &*next(y); };
if (bad(y)) { erase(y); return; }
while (next(y) != end() && bad(next(y))) erase(next(y));
while (y != begin() && bad(prev(y))) erase(prev(y));
}
LL eval(LL x) {
auto l = *lower_bound((Line){x, is_query});
return l.m * x + l.b;
}
};
// Usage
HullDynamic cht;
cht.insert_line(slope, constant);
min_val = cht.eval(x_val); // min value at point x_val
source: Tanuj Khattar https://pastebin.com/XF5WQ2YR
Maintains Upper hull for maximum
typedef long long int64;
typedef long double float128;
const int64 is_query = -(1LL<<62), inf = 1e18;
struct Line {
int64 m, b;
mutable function<const Line*()> succ;
bool operator<(const Line& rhs) const {
if (rhs.b != is_query) return m < rhs.m;
const Line* s = succ();
if (!s) return 0;
int64 x = rhs.m;
return b - s->b < (s->m - m) * x;
}
};
struct HullDynamic : public multiset<Line> { // will maintain upper hull for maximum
bool bad(iterator y) {
auto z = next(y);
if (y == begin()) {
if (z == end()) return 0;
return y->m == z->m && y->b <= z->b;
}
auto x = prev(y);
if (z == end()) return y->m == x->m && y->b <= x->b;
return (float128)(x->b - y->b)*(z->m - y->m) >= (float128)(y->b - z->b)*(y->m - x->m);
}
void insert_line(int64 m, int64 b) {
auto y = insert({ m, b });
y->succ = [=] { return next(y) == end() ? 0 : &*next(y); };
if (bad(y)) { erase(y); return; }
while (next(y) != end() && bad(next(y))) erase(next(y));
while (y != begin() && bad(prev(y))) erase(prev(y));
}
int64 eval(int64 x) {
auto l = *lower_bound((Line) { x, is_query });
return l.m * x + l.b;
}
};
https://github.com/SuprDewd/CompetitiveProgramming/blob/master/code/data-structures/convex_hull_trick_dynamic.cpp, https://csacademy.com/submission/1357076/
Li-Chao Segment Trees can be used to solve problems of the format:
You're given a set S
containing function of the same "type" (ex. lines, y = ax + b
). The type of function need to have the transcending property. You need to handle two type of queries:
S
x = t
considering all functions in S
A type of function has transcending property if:
Given two functions f(x)
, g(x)
of that type, if f(t)
is greater than/smaller than g(t)
for some x = t
, then f(x)
will be greater than/smaller than g(x)
for x > t
. In other words, once f(x)
"win/lose" g(x)
, f(x)
will continue to "win/lose" g(x)
.
Since it's basically a segment tree, it has a big advantage over convex hull: supporting segments. It's also easier to code, and the order of slope doesn't matter.
Disadvantages:
x
-coordinates (offline approach) or use dynamic
segment tree (online approach) to support large x
-coordinates. This is not a problem with convex hull.TODO: https://csacademy.com/contest/archive/task/squared-ends/statistics/
#include <bits/stdc++.h>
using namespace std;
template< typename T, bool ismax >
struct LiChaoTree {
struct Line {
T a, b;
Line(T a, T b) : a(a), b(b) {}
inline T get(T x) const { return a * x + b; }
inline bool over(const Line &b, const T &x) const {
return (get(x) < b.get(x)) ^ ismax;
}
};
vector< T > xs;
vector< Line > seg;
int sz;
LiChaoTree(const vector< T > &x, T INF) : xs(x) {
sz = 1;
while(sz < xs.size()) sz <<= 1;
while(xs.size() < sz) xs.push_back(xs.back() + 1);
seg.assign(2 * sz - 1, Line(0, INF));
}
void update(Line &x, int k, int l, int r) {
auto latte = x.over(seg[k], xs[l]), malta = x.over(seg[k], xs[r - 1]);
if(latte & malta) {
seg[k] = x;
} else if(latte ^ malta) {
int mid = (l + r) >> 1;
auto beet = x.over(seg[k], xs[mid]);
if(beet) swap(seg[k], x);
if(latte != beet) update(x, 2 * k + 1, l, mid);
else update(x, 2 * k + 2, mid, r);
}
}
void update(T a, T b) { // ax+b
Line l(a, b);
update(l, 0, 0, sz);
}
T query(int k) { // xs[k]
const T x = xs[k];
k += sz - 1;
T ret = seg[k].get(x);
while(k > 0) {
k = (k - 1) >> 1;
if(ismax) ret = max(ret, seg[k].get(x));
else ret = min(ret, seg[k].get(x));
}
return ret;
}
};
using int64 = long long;
const int64 INF = 1LL << 40;
int main() {
int N, K;
cin >> N >> K;
vector< int64 > A(N);
for(int i = 0; i < N; i++) cin >> A[i];
auto B(A);
sort(begin(B), end(B));
B.erase(unique(begin(B), end(B)), end(B));
vector< int > C(N);
for(int i = 0; i < N; i++) {
C[i] = lower_bound(begin(B), end(B), A[i]) - begin(B);
}
vector< int64 > dp(N + 1, INF);
dp[0] = 0;
for(int i = 0; i < K; i++) {
vector< int64 > dp2(N + 1, INF);
for(int j = 0; j < N; j++) dp[j] += A[j] * A[j];
LiChaoTree< int64, false > chd(B, INF);
if(dp[0] < INF) chd.update(-2 * A[0], dp[0]);
for(int j = 1; j <= N; j++) {
dp2[j] = chd.query(C[j - 1]) + A[j - 1] * A[j - 1];
if(dp[j] < INF) chd.update(-2 * A[j], dp[j]);
}
dp.swap(dp2);
}
cout << dp[N] << endl;
}