Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
DP_Optimizations.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
54# ifndef DP_OPTIMIZATIONS_H
55# define DP_OPTIMIZATIONS_H
56
57# include <algorithm>
58# include <cstddef>
59# include <limits>
60# include <numeric>
61# include <type_traits>
62# include <utility>
63
64# include <ah-errors.H>
65# include <tpl_array.H>
66
67namespace Aleph
68{
69 namespace dp_optimization_detail
70 {
71 template <typename T>
72 using promoted_t = std::conditional_t<std::is_floating_point_v<T>, T,
73 std::conditional_t<(sizeof(T) < 8), long long, __int128_t>>;
74
75 template <typename T>
77 {
78 if constexpr (std::numeric_limits<T>::has_infinity)
79 return std::numeric_limits<T>::infinity();
80 else
81 return std::numeric_limits<T>::max() / 4;
82 }
83
84 template <typename Target, typename Source>
85 [[nodiscard]] constexpr Target clamped_cast(Source val) noexcept
86 {
87 if (val >= static_cast<Source>(std::numeric_limits<Target>::max()))
88 return std::numeric_limits<Target>::max();
89 if (val <= static_cast<Source>(std::numeric_limits<Target>::min()))
90 return std::numeric_limits<Target>::min();
91 return static_cast<Target>(val);
92 }
93 } // namespace dp_optimization_detail
94
95
98 template <typename Cost>
105
106
131 template <typename Cost, class Transition_Cost_Fn>
134 const size_t n,
136 const Cost inf =
137 dp_optimization_detail::default_inf<Cost>())
138 {
139 Array<Cost> prev = Array<Cost>::create(n + 1);
140 Array<Cost> curr = Array<Cost>::create(n + 1);
141
142 for (size_t i = 0; i <= n; ++i)
143 {
144 prev(i) = inf;
145 curr(i) = inf;
146 }
147 prev(0) = Cost{};
148
150 split.reserve(groups + 1);
151 for (size_t g = 0; g <= groups; ++g)
152 {
154 for (size_t i = 0; i <= n; ++i)
155 row(i) = 0;
156 split.append(std::move(row));
157 }
158
159 for (size_t g = 1; g <= groups; ++g)
160 {
161 for (size_t i = 0; i <= n; ++i)
162 curr(i) = inf;
163
164 if (g <= n)
165 {
166 auto solve = [&](auto && self,
167 const size_t left, const size_t right,
168 const size_t opt_left, const size_t opt_right) -> void
169 {
170 if (left > right)
171 return;
172
173 const size_t mid = std::midpoint(left, right);
174 const size_t k_end = std::min(mid - 1, opt_right);
175
176 Cost best = inf;
177 size_t best_k = opt_left;
178
179 for (size_t k = opt_left; k <= k_end; ++k)
180 {
181 if (prev[k] >= inf)
182 continue;
183
184 const Cost tc = transition_cost(k, mid);
185 Cost cand;
186 if (tc >= inf or prev[k] > inf - tc)
187 cand = inf;
188 else
189 cand = prev[k] + tc;
190
191 if (cand < best)
192 {
193 best = cand;
194 best_k = k;
195 }
196 }
197
198 curr(mid) = best;
199 split[g](mid) = best_k;
200
201 if (left < mid)
202 self(self, left, mid - 1, opt_left, best_k);
203 if (mid < right)
204 self(self, mid + 1, right, best_k, opt_right);
205 };
206
207 solve(solve, g, n, g - 1, n - 1);
208 }
209
210 prev.swap(curr);
211 }
212
214 prev[n],
215 std::move(prev),
216 std::move(split)
217 };
218 }
219
220
223 template <typename Cost>
230
231
254 template <typename Cost, class Interval_Cost_Fn>
258 const Cost inf =
259 dp_optimization_detail::default_inf<Cost>())
260 {
262 dp.reserve(n + 1);
263 for (size_t i = 0; i <= n; ++i)
264 {
265 Array<Cost> row = Array<Cost>::create(n + 1);
266 for (size_t j = 0; j <= n; ++j)
267 row(j) = inf;
268 dp.append(std::move(row));
269 }
270
272 opt.reserve(n + 1);
273 for (size_t i = 0; i <= n; ++i)
274 {
276 for (size_t j = 0; j <= n; ++j)
277 row(j) = 0;
278 opt.append(std::move(row));
279 }
280
281 for (size_t i = 0; i <= n; ++i)
282 {
283 dp(i)(i) = Cost{};
284 opt(i)(i) = i;
285 if (i + 1 <= n)
286 {
287 dp(i)(i + 1) = Cost{};
288 opt(i)(i + 1) = i + 1;
289 }
290 }
291
292 for (size_t len = 2; len <= n; ++len)
293 for (size_t i = 0; i + len <= n; ++i)
294 {
295 const size_t j = i + len;
296
297 size_t left = opt[i][j - 1];
298 size_t right = opt[i + 1][j];
299 if (left < i + 1)
300 left = i + 1;
301 if (right > j - 1)
302 right = j - 1;
303
304 ah_runtime_error_if(left > right)
305 << "knuth_optimize_interval: invalid opt bounds at ["
306 << i << ", " << j << ")";
307
308 const Cost w = interval_cost(i, j);
309
310 Cost best = inf;
311 size_t best_k = left;
312
313 for (size_t k = left; k <= right; ++k)
314 {
315 const Cost d1 = dp[i][k];
316 const Cost d2 = dp[k][j];
317 Cost cand;
318 if (d1 >= inf or d2 >= inf or w >= inf or d1 > inf - d2 or (d1 + d2) > inf - w)
319 cand = inf;
320 else
321 cand = d1 + d2 + w;
322
323 if (cand < best)
324 {
325 best = cand;
326 best_k = k;
327 }
328 }
329
330 dp(i)(j) = best;
331 opt(i)(j) = best_k;
332 }
333
335 n == 0 ? Cost{} : dp[0][n],
336 std::move(dp),
337 std::move(opt)
338 };
339 }
340
341
354 {
355 const size_t n = weights.size();
357 prefix(0) = 0;
358
359 for (size_t i = 0; i < n; ++i)
360 {
362 std::numeric_limits<size_t>::max() - weights[i])
363 << "optimal_merge_knuth: prefix sum overflow";
364 prefix(i + 1) = prefix[i] + weights[i];
365 }
366
368 n,
369 [&](const size_t i, const size_t j) -> size_t
370 {
371 return prefix[j] - prefix[i];
372 },
373 std::numeric_limits<size_t>::max() / 4
374 );
375 }
376
377
386 template <typename T>
388 {
389 static_assert(std::is_signed_v<T>, "Convex_Hull_Trick requires a signed type");
390
391 public:
393 struct Line
394 {
395 T slope = T{};
397
398 [[nodiscard]] T value_at(const T x) const noexcept
399 {
401 return static_cast<T>(static_cast<PromotedT>(slope) * static_cast<PromotedT>(x) +
402 static_cast<PromotedT>(intercept));
403 }
404 };
405
406 private:
408 Array<long double> starts_; // x where this line becomes optimal
409 size_t cursor_ = 0; // for monotone queries
410
411 [[nodiscard]] static long double intersection_x(const Line & a,
412 const Line & b) noexcept
413 {
414 return static_cast<long double>(b.intercept - a.intercept)
415 / static_cast<long double>(a.slope - b.slope);
416 }
417
418 public:
419 [[nodiscard]] size_t size() const noexcept { return lines_.size(); }
420 [[nodiscard]] bool is_empty() const noexcept { return lines_.size() == 0; }
421
422 void clear()
423 {
426
429
430 cursor_ = 0;
431 }
432
434 {
435 cursor_ = 0;
436 }
437
439 void add_line(const T slope, const T intercept)
440 {
441 Line ln{slope, intercept};
442
443 if (lines_.size() > 0)
444 {
445 const Line & last = lines_[lines_.size() - 1];
446 ah_domain_error_if(ln.slope > last.slope)
447 << "Convex_Hull_Trick::add_line: slopes must be non-increasing";
448
449 // Keep only the best intercept for equal slope.
450 if (ln.slope == last.slope)
451 {
452 if (ln.intercept >= last.intercept)
453 return;
454
455 (void) lines_.remove_last();
457 if (cursor_ > lines_.size())
458 cursor_ = lines_.size();
459 }
460 }
461
462 while (lines_.size() > 0)
463 {
464 const long double x = intersection_x(lines_[lines_.size() - 1], ln);
465
466 if (lines_.size() == 1 or x > starts_[starts_.size() - 1])
467 {
468 starts_.append(x);
469 lines_.append(ln);
470 if (cursor_ >= lines_.size())
471 cursor_ = lines_.size() - 1;
472 return;
473 }
474
475 (void) lines_.remove_last();
477 if (cursor_ > lines_.size())
478 cursor_ = lines_.size();
479 }
480
481 starts_.append(-std::numeric_limits<long double>::infinity());
482 lines_.append(ln);
483 if (cursor_ >= lines_.size())
484 cursor_ = lines_.size() - 1;
485 }
486
488 [[nodiscard]] T query(const T x) const
489 {
490 ah_runtime_error_if(lines_.size() == 0)
491 << "Convex_Hull_Trick::query: no lines available";
492
493 size_t lo = 0;
494 size_t hi = lines_.size() - 1;
495 const auto xd = static_cast<long double>(x);
496
497 while (lo < hi)
498 if (const size_t mid = std::midpoint(lo, hi + 1); starts_[mid] <= xd)
499 lo = mid;
500 else
501 hi = mid - 1;
502
503 return lines_[lo].value_at(x);
504 }
505
508 {
509 ah_runtime_error_if(lines_.size() == 0)
510 << "Convex_Hull_Trick::query_monotone: no lines available";
511
512 while (cursor_ + 1 < lines_.size()
513 and starts_[cursor_ + 1] <= static_cast<long double>(x))
514 ++cursor_;
515
516 return lines_[cursor_].value_at(x);
517 }
518 };
519
520
528 template <typename T>
530 {
531 static_assert(std::is_integral_v<T> and std::is_signed_v<T>,
532 "Li_Chao_Tree requires a signed integral coordinate/value type");
533
534 public:
536 struct Line
537 {
538 T slope = T{};
540
541 [[nodiscard]] T value_at(const T x) const noexcept
542 {
544 return static_cast<T>(static_cast<PromotedT>(slope) * static_cast<PromotedT>(x) +
545 static_cast<PromotedT>(intercept));
546 }
547 };
548
549 private:
550 struct Node
551 {
553 size_t left = std::numeric_limits<size_t>::max();
554 size_t right = std::numeric_limits<size_t>::max();
555 };
556
557 static constexpr size_t NIL = std::numeric_limits<size_t>::max();
558
562 size_t root_ = NIL;
563
564 [[nodiscard]] size_t new_node(const Line & line)
565 {
566 nodes_.append(Node{line, NIL, NIL});
567 return nodes_.size() - 1;
568 }
569
570 void add_line_impl(const size_t idx, const T l, const T r, Line line)
571 {
572 const T mid = std::midpoint(l, r);
573
574 if (line.value_at(mid) < nodes_[idx].line.value_at(mid))
575 std::swap(line, nodes_[idx].line);
576
577 if (l == r)
578 return;
579
580 if (line.value_at(l) < nodes_[idx].line.value_at(l))
581 {
582 if (nodes_[idx].left == NIL)
583 {
584 nodes_[idx].left = new_node(line);
585 return;
586 }
587 add_line_impl(nodes_[idx].left, l, mid, line);
588 return;
589 }
590
591 if (line.value_at(r) < nodes_[idx].line.value_at(r))
592 {
593 if (nodes_[idx].right == NIL)
594 {
595 nodes_[idx].right = new_node(line);
596 return;
597 }
598 add_line_impl(nodes_[idx].right, mid + 1, r, line);
599 }
600 }
601
602 [[nodiscard]] T query_impl(const size_t idx,
603 const T l, const T r,
604 const T x) const
605 {
607 auto ret = static_cast<PromotedT>(nodes_[idx].line.value_at(x));
608 if (l == r)
609 return static_cast<T>(ret);
610
611 const T mid = std::midpoint(l, r);
612 if (x <= mid and nodes_[idx].left != NIL)
613 ret = std::min(ret, static_cast<PromotedT>(query_impl(nodes_[idx].left, l, mid, x)));
614 else if (x > mid and nodes_[idx].right != NIL)
615 ret = std::min(ret, static_cast<PromotedT>(query_impl(nodes_[idx].right, mid + 1, r, x)));
616
617 return dp_optimization_detail::clamped_cast<T>(ret);
618 }
619
620 public:
623 {
625 << "Li_Chao_Tree: invalid domain [" << x_left_ << ", " << x_right_ << "]";
626 }
627
628 [[nodiscard]] bool is_empty() const noexcept { return root_ == NIL; }
629 [[nodiscard]] size_t node_count() const noexcept { return nodes_.size(); }
630
631 void clear()
632 {
634 nodes_.swap(tmp);
635 root_ = NIL;
636 }
637
638 void add_line(const T slope, const T intercept)
639 {
640 const Line line{slope, intercept};
641 if (root_ == NIL)
642 {
643 root_ = new_node(line);
644 return;
645 }
647 }
648
649 [[nodiscard]] T query(const T x) const
650 {
652 << "Li_Chao_Tree::query: no lines available";
654 << "Li_Chao_Tree::query: x=" << x
655 << " outside domain [" << x_left_ << ", " << x_right_ << "]";
656
657 return query_impl(root_, x_left_, x_right_, x);
658 }
659 };
660
661
664 template <typename Cost>
670
671
691 template <typename Cost>
694 const size_t window,
695 const Cost inf =
696 dp_optimization_detail::default_inf<Cost>())
697 {
698 const size_t n = base_cost.size();
699 if (n == 0)
701
702 ah_domain_error_if(window == 0 and n > 1)
703 << "monotone_queue_min_dp: window must be > 0 when n > 1";
704
707
709 size_t head = 0;
710
711 dp(0) = base_cost[0];
712 parent(0) = 0;
713 deque_idx.append(0);
714
715 for (size_t i = 1; i < n; ++i)
716 {
717 const size_t min_valid = i > window ? i - window : 0;
718
719 while (head < deque_idx.size() and deque_idx[head] < min_valid)
720 ++head;
721
722 ah_runtime_error_if(head == deque_idx.size())
723 << "monotone_queue_min_dp: no valid predecessor for i=" << i;
724
725 const size_t best = deque_idx[head];
726 const Cost bc = base_cost[i];
727 if (dp[best] >= inf or bc >= inf or dp[best] > inf - bc)
728 dp(i) = inf;
729 else
730 dp(i) = dp[best] + bc;
731
732 parent(i) = best;
733
734 while (deque_idx.size() > head and dp[deque_idx[deque_idx.size() - 1]] >= dp[i])
736
737 deque_idx.append(i);
738 }
739
740 return Monotone_Queue_DP_Result<Cost>{std::move(dp), std::move(parent)};
741 }
742
743
764 template <typename T>
765 [[nodiscard]] inline Array<T>
767 const Array<T> & weights)
768 {
769 static_assert(std::is_integral_v<T> and std::is_signed_v<T>,
770 "min_weighted_squared_distance_1d requires signed integral T");
771
772 ah_domain_error_if(xs.size() != weights.size())
773 << "min_weighted_squared_distance_1d: xs and weights size mismatch";
774
775 const size_t n = xs.size();
776 if (n == 0)
777 return Array<T>();
778
779 T min_x = xs[0];
780 T max_x = xs[0];
781 for (size_t i = 1; i < n; ++i)
782 {
783 if (xs[i] < min_x)
784 min_x = xs[i];
785 if (xs[i] > max_x)
786 max_x = xs[i];
787 }
788
789 Li_Chao_Tree<T> lc(min_x, max_x);
791 for (size_t j = 0; j < n; ++j)
792 {
793 const auto px = static_cast<PromotedT>(xs[j]);
794 const T m = static_cast<T>(static_cast<PromotedT>(-2) * px);
795 const T b = static_cast<T>(px * px + static_cast<PromotedT>(weights[j]));
796 lc.add_line(m, b);
797 }
798
800 for (size_t i = 0; i < n; ++i)
801 {
802 const auto px = static_cast<PromotedT>(xs[i]);
803 ret(i) = static_cast<T>(px * px + static_cast<PromotedT>(lc.query(xs[i])));
804 }
805
806 return ret;
807 }
808} // namespace Aleph
809
810# endif // DP_OPTIMIZATIONS_H
Exception handling system with formatted messages for Aleph-w.
#define ah_out_of_range_error_if(C)
Throws std::out_of_range if condition holds.
Definition ah-errors.H:579
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:522
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:266
long double w
Definition btreepic.C:153
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:194
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
void swap(Array &s) noexcept
Swap this with s
Definition tpl_array.H:227
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Convex Hull Trick for minimum queries.
static long double intersection_x(const Line &a, const Line &b) noexcept
bool is_empty() const noexcept
T query_monotone(const T x)
Query minimum with non-decreasing x (amortized O(1)).
void reset_query_cursor() noexcept
T query(const T x) const
Query minimum value at arbitrary x (O(log n)).
size_t size() const noexcept
void add_line(const T slope, const T intercept)
Insert a new line; slopes must be non-increasing.
Array< long double > starts_
Li Chao tree for min line queries on an integral x-domain.
void add_line(const T slope, const T intercept)
static constexpr size_t NIL
T query(const T x) const
T query_impl(const size_t idx, const T l, const T r, const T x) const
bool is_empty() const noexcept
void add_line_impl(const size_t idx, const T l, const T r, Line line)
size_t node_count() const noexcept
size_t new_node(const Line &line)
Li_Chao_Tree(const T x_left, const T x_right)
std::conditional_t< std::is_floating_point_v< T >, T, std::conditional_t<(sizeof(T)< 8), long long, __int128_t > > promoted_t
constexpr T default_inf() noexcept
constexpr Target clamped_cast(Source val) noexcept
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
DynList< T > intercept(const Container< T > &c1, const Container< T > &c2)
Array< T > min_weighted_squared_distance_1d(const Array< T > &xs, const Array< T > &weights)
Weighted squared-distance lower envelope on a line.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
Knuth_Optimization_Result< size_t > optimal_merge_knuth(const Array< size_t > &weights)
Optimal adjacent merge cost via Knuth optimization.
static void prefix(Node *root, DynList< Node * > &acc)
Monotone_Queue_DP_Result< Cost > monotone_queue_min_dp(const Array< Cost > &base_cost, const size_t window, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize windowed min-transition DP with a monotone queue.
std::vector< std::string > & split(const std::string &s, const char delim, std::vector< std::string > &elems)
Split a std::string by a single delimiter character.
Knuth_Optimization_Result< Cost > knuth_optimize_interval(const size_t n, Interval_Cost_Fn interval_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize interval DP with Knuth optimization.
Affine line y = m*x + b.
T value_at(const T x) const noexcept
Result of divide-and-conquer partition DP optimization.
Array< Cost > last_row
Last DP layer (size n+1).
Array< Array< size_t > > split
Best split index per layer/state.
Cost optimal_cost
Final optimum at state (groups, n).
Result of Knuth interval-DP optimization.
Array< Array< Cost > > dp
Interval DP table.
Cost optimal_cost
Final optimum at interval [0, n).
Array< Array< size_t > > opt
Argmin table used by Knuth bounds.
Affine line y = m*x + b.
T value_at(const T x) const noexcept
Result of monotone-queue windowed DP transition.
Array< size_t > parent
Chosen predecessor index for each i.
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
static int * k
gsl_rng * r
Dynamic array container with automatic resizing.
DynList< int > l