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
53#ifndef DP_OPTIMIZATIONS_H
54#define DP_OPTIMIZATIONS_H
55
56#include <algorithm>
57#include <cstddef>
58#include <limits>
59#include <numeric>
60#include <type_traits>
61#include <utility>
62
63#include <ah-errors.H>
64#include <tpl_array.H>
65
66namespace Aleph {
67namespace dp_optimization_detail {
68// MSVC cl has no __int128_t; use long double to avoid signed-integer
69// overflow UB in intermediate computations (clamping happens at the cast site).
70#if defined(_MSC_VER) && !defined(__clang__)
71using _promoted_int_t = long double;
72#else
74#endif
75
76template <typename T>
78 std::conditional_t<std::is_floating_point_v<T>,
79 T,
80 std::conditional_t<(sizeof(T) < 8), long long, _promoted_int_t>>;
81
82template <typename T>
84{
85 if constexpr (std::numeric_limits<T>::has_infinity)
86 return std::numeric_limits<T>::infinity();
87 else
88 return std::numeric_limits<T>::max() / 4;
89}
90
91template <typename Target, typename Source>
92[[nodiscard]] constexpr Target clamped_cast(Source val) noexcept
93{
94 if (val >= static_cast<Source>(std::numeric_limits<Target>::max()))
95 return std::numeric_limits<Target>::max();
96 if (val <= static_cast<Source>(std::numeric_limits<Target>::min()))
97 return std::numeric_limits<Target>::min();
98 return static_cast<Target>(val);
99}
100} // namespace dp_optimization_detail
101
104template <typename Cost>
111
136template <typename Cost, class Transition_Cost_Fn>
138 const size_t groups,
139 const size_t n,
141 const Cost inf = dp_optimization_detail::default_inf<Cost>())
142{
143 Array<Cost> prev = Array<Cost>::create(n + 1);
144 Array<Cost> curr = Array<Cost>::create(n + 1);
145
146 for (size_t i = 0; i <= n; ++i)
147 {
148 prev(i) = inf;
149 curr(i) = inf;
150 }
151 prev(0) = Cost{};
152
154 split.reserve(groups + 1);
155 for (size_t g = 0; g <= groups; ++g)
156 {
158 for (size_t i = 0; i <= n; ++i)
159 row(i) = 0;
160 split.append(std::move(row));
161 }
162
163 for (size_t g = 1; g <= groups; ++g)
164 {
165 for (size_t i = 0; i <= n; ++i)
166 curr(i) = inf;
167
168 if (g <= n)
169 {
170 auto solve = [&](auto &&self, const size_t left, const size_t right,
171 const size_t opt_left, const size_t opt_right) -> void
172 {
173 if (left > right)
174 return;
175
176 const size_t mid = std::midpoint(left, right);
177 const size_t k_end = std::min(mid - 1, opt_right);
178
179 Cost best = inf;
180 size_t best_k = opt_left;
181
182 for (size_t k = opt_left; k <= k_end; ++k)
183 {
184 if (prev[k] >= inf)
185 continue;
186
187 const Cost tc = transition_cost(k, mid);
188 Cost cand;
189 if (tc >= inf or prev[k] > inf - tc)
190 cand = inf;
191 else
192 cand = prev[k] + tc;
193
194 if (cand < best)
195 {
196 best = cand;
197 best_k = k;
198 }
199 }
200
201 curr(mid) = best;
202 split[g](mid) = best_k;
203
204 if (left < mid)
205 self(self, left, mid - 1, opt_left, best_k);
206 if (mid < right)
207 self(self, mid + 1, right, best_k, opt_right);
208 };
209
210 solve(solve, g, n, g - 1, n - 1);
211 }
212
213 prev.swap(curr);
214 }
215
216 return Divide_Conquer_DP_Result<Cost>{prev[n], std::move(prev), std::move(split)};
217}
218
221template <typename Cost>
228
251template <typename Cost, class Interval_Cost_Fn>
253 const size_t n,
255 const Cost inf = dp_optimization_detail::default_inf<Cost>())
256{
258 dp.reserve(n + 1);
259 for (size_t i = 0; i <= n; ++i)
260 {
262 for (size_t j = 0; j <= n; ++j)
263 row(j) = inf;
264 dp.append(std::move(row));
265 }
266
268 opt.reserve(n + 1);
269 for (size_t i = 0; i <= n; ++i)
270 {
272 for (size_t j = 0; j <= n; ++j)
273 row(j) = 0;
274 opt.append(std::move(row));
275 }
276
277 for (size_t i = 0; i <= n; ++i)
278 {
279 dp(i)(i) = Cost{};
280 opt(i)(i) = i;
281 if (i + 1 <= n)
282 {
283 dp(i)(i + 1) = Cost{};
284 opt(i)(i + 1) = i + 1;
285 }
286 }
287
288 for (size_t len = 2; len <= n; ++len)
289 for (size_t i = 0; i + len <= n; ++i)
290 {
291 const size_t j = i + len;
292
293 size_t left = opt[i][j - 1];
294 size_t right = opt[i + 1][j];
295 if (left < i + 1)
296 left = i + 1;
297 if (right > j - 1)
298 right = j - 1;
299
300 ah_runtime_error_if(left > right)
301 << "knuth_optimize_interval: invalid opt bounds at [" << i << ", " << j << ")";
302
303 const Cost w = interval_cost(i, j);
304
305 Cost best = inf;
306 size_t best_k = left;
307
308 for (size_t k = left; k <= right; ++k)
309 {
310 const Cost d1 = dp[i][k];
311 const Cost d2 = dp[k][j];
312 Cost cand;
313 if (d1 >= inf or d2 >= inf or w >= inf or d1 > inf - d2 or (d1 + d2) > inf - w)
314 cand = inf;
315 else
316 cand = d1 + d2 + w;
317
318 if (cand < best)
319 {
320 best = cand;
321 best_k = k;
322 }
323 }
324
325 dp(i)(j) = best;
326 opt(i)(j) = best_k;
327 }
328
329 return Knuth_Optimization_Result<Cost>{n == 0 ? Cost{} : dp[0][n], std::move(dp), std::move(opt)};
330}
331
343{
344 const size_t n = weights.size();
346 prefix(0) = 0;
347
348 for (size_t i = 0; i < n; ++i)
349 {
350 ah_runtime_error_if(prefix[i] > std::numeric_limits<size_t>::max() - weights[i])
351 << "optimal_merge_knuth: prefix sum overflow";
352 prefix(i + 1) = prefix[i] + weights[i];
353 }
354
355 return knuth_optimize_interval<size_t>(n, [&](const size_t i, const size_t j) -> size_t
356 {
357 return prefix[j] - prefix[i];
358 }, std::numeric_limits<size_t>::max() / 4);
359}
360
369template <typename T>
371{
372 static_assert(std::is_signed_v<T>, "Convex_Hull_Trick requires a signed type");
373
374public:
376 struct Line
377 {
378 T slope = T{};
380
381 [[nodiscard]] T value_at(const T x) const noexcept
382 {
384 return static_cast<T>(static_cast<PromotedT>(slope) * static_cast<PromotedT>(x) +
385 static_cast<PromotedT>(intercept));
386 }
387 };
388
389private:
391 Array<long double> starts_; // x where this line becomes optimal
392 size_t cursor_ = 0; // for monotone queries
393
394 [[nodiscard]] static long double intersection_x(const Line &a, const Line &b) noexcept
395 {
396 return static_cast<long double>(b.intercept - a.intercept) /
397 static_cast<long double>(a.slope - b.slope);
398 }
399
400public:
402 {
403 return lines_.size();
404 }
405
407 {
408 return lines_.size() == 0;
409 }
410
411 void clear()
412 {
415
418
419 cursor_ = 0;
420 }
421
423 {
424 cursor_ = 0;
425 }
426
428 void add_line(const T slope, const T intercept)
429 {
430 Line ln{slope, intercept};
431
432 if (lines_.size() > 0)
433 {
434 const Line &last = lines_[lines_.size() - 1];
435 ah_domain_error_if(ln.slope > last.slope)
436 << "Convex_Hull_Trick::add_line: slopes must be non-increasing";
437
438 // Keep only the best intercept for equal slope.
439 if (ln.slope == last.slope)
440 {
441 if (ln.intercept >= last.intercept)
442 return;
443
444 (void) lines_.remove_last();
446 if (cursor_ > lines_.size())
447 cursor_ = lines_.size();
448 }
449 }
450
451 while (lines_.size() > 0)
452 {
453 const long double x = intersection_x(lines_[lines_.size() - 1], ln);
454
455 if (lines_.size() == 1 or x > starts_[starts_.size() - 1])
456 {
457 starts_.append(x);
458 lines_.append(ln);
459 if (cursor_ >= lines_.size())
460 cursor_ = lines_.size() - 1;
461 return;
462 }
463
464 (void) lines_.remove_last();
466 if (cursor_ > lines_.size())
467 cursor_ = lines_.size();
468 }
469
470 starts_.append(-std::numeric_limits<long double>::infinity());
471 lines_.append(ln);
472 if (cursor_ >= lines_.size())
473 cursor_ = lines_.size() - 1;
474 }
475
477 [[nodiscard]] T query(const T x) const
478 {
479 ah_runtime_error_if(lines_.size() == 0) << "Convex_Hull_Trick::query: no lines available";
480
481 size_t lo = 0;
482 size_t hi = lines_.size() - 1;
483 const auto xd = static_cast<long double>(x);
484
485 while (lo < hi)
486 if (const size_t mid = std::midpoint(lo, hi + 1); starts_[mid] <= xd)
487 lo = mid;
488 else
489 hi = mid - 1;
490
491 return lines_[lo].value_at(x);
492 }
493
496 {
497 ah_runtime_error_if(lines_.size() == 0)
498 << "Convex_Hull_Trick::query_monotone: no lines available";
499
500 while (cursor_ + 1 < lines_.size() and starts_[cursor_ + 1] <= static_cast<long double>(x))
501 ++cursor_;
502
503 return lines_[cursor_].value_at(x);
504 }
505};
506
514template <typename T>
516{
517 static_assert(std::is_integral_v<T> and std::is_signed_v<T>,
518 "Li_Chao_Tree requires a signed integral coordinate/value type");
519
520public:
522 struct Line
523 {
524 T slope = T{};
526
527 [[nodiscard]] T value_at(const T x) const noexcept
528 {
530 return static_cast<T>(static_cast<PromotedT>(slope) * static_cast<PromotedT>(x) +
531 static_cast<PromotedT>(intercept));
532 }
533 };
534
535private:
536 struct Node
537 {
539 size_t left = std::numeric_limits<size_t>::max();
540 size_t right = std::numeric_limits<size_t>::max();
541 };
542
543 static constexpr size_t NIL = std::numeric_limits<size_t>::max();
544
548 size_t root_ = NIL;
549
550 [[nodiscard]] size_t new_node(const Line &line)
551 {
552 nodes_.append(Node{line, NIL, NIL});
553 return nodes_.size() - 1;
554 }
555
556 void add_line_impl(const size_t idx, const T l, const T r, Line line)
557 {
558 const T mid = std::midpoint(l, r);
559
560 if (line.value_at(mid) < nodes_[idx].line.value_at(mid))
561 std::swap(line, nodes_[idx].line);
562
563 if (l == r)
564 return;
565
566 if (line.value_at(l) < nodes_[idx].line.value_at(l))
567 {
568 if (nodes_[idx].left == NIL)
569 {
570 nodes_[idx].left = new_node(line);
571 return;
572 }
573 add_line_impl(nodes_[idx].left, l, mid, line);
574 return;
575 }
576
577 if (line.value_at(r) < nodes_[idx].line.value_at(r))
578 {
579 if (nodes_[idx].right == NIL)
580 {
581 nodes_[idx].right = new_node(line);
582 return;
583 }
584 add_line_impl(nodes_[idx].right, mid + 1, r, line);
585 }
586 }
587
588 [[nodiscard]] T query_impl(const size_t idx, const T l, const T r, const T x) const
589 {
591 auto ret = static_cast<PromotedT>(nodes_[idx].line.value_at(x));
592 if (l == r)
593 return static_cast<T>(ret);
594
595 const T mid = std::midpoint(l, r);
596 if (x <= mid and nodes_[idx].left != NIL)
597 ret = std::min(ret, static_cast<PromotedT>(query_impl(nodes_[idx].left, l, mid, x)));
598 else if (x > mid and nodes_[idx].right != NIL)
599 ret = std::min(ret, static_cast<PromotedT>(query_impl(nodes_[idx].right, mid + 1, r, x)));
600
601 return dp_optimization_detail::clamped_cast<T>(ret);
602 }
603
604public:
606 {
608 << "Li_Chao_Tree: invalid domain [" << x_left_ << ", " << x_right_ << "]";
609 }
610
612 {
613 return root_ == NIL;
614 }
616 {
617 return nodes_.size();
618 }
619
620 void clear()
621 {
623 nodes_.swap(tmp);
624 root_ = NIL;
625 }
626
627 void add_line(const T slope, const T intercept)
628 {
629 const Line line{slope, intercept};
630 if (root_ == NIL)
631 {
632 root_ = new_node(line);
633 return;
634 }
636 }
637
638 [[nodiscard]] T query(const T x) const
639 {
640 ah_runtime_error_if(root_ == NIL) << "Li_Chao_Tree::query: no lines available";
642 << "Li_Chao_Tree::query: x=" << x << " outside domain [" << x_left_ << ", " << x_right_ << "]";
643
644 return query_impl(root_, x_left_, x_right_, x);
645 }
646};
647
650template <typename Cost>
656
676template <typename Cost>
678 const Array<Cost> &base_cost,
679 const size_t window,
680 const Cost inf = dp_optimization_detail::default_inf<Cost>())
681{
682 const size_t n = base_cost.size();
683 if (n == 0)
685
686 ah_domain_error_if(window == 0 and n > 1)
687 << "monotone_queue_min_dp: window must be > 0 when n > 1";
688
691
693 size_t head = 0;
694
695 dp(0) = base_cost[0];
696 parent(0) = 0;
697 deque_idx.append(0);
698
699 for (size_t i = 1; i < n; ++i)
700 {
701 const size_t min_valid = i > window ? i - window : 0;
702
703 while (head < deque_idx.size() and deque_idx[head] < min_valid)
704 ++head;
705
706 ah_runtime_error_if(head == deque_idx.size())
707 << "monotone_queue_min_dp: no valid predecessor for i=" << i;
708
709 const size_t best = deque_idx[head];
710 const Cost bc = base_cost[i];
711 if (dp[best] >= inf or bc >= inf or dp[best] > inf - bc)
712 dp(i) = inf;
713 else
714 dp(i) = dp[best] + bc;
715
716 parent(i) = best;
717
718 while (deque_idx.size() > head and dp[deque_idx[deque_idx.size() - 1]] >= dp[i])
720
721 deque_idx.append(i);
722 }
723
724 return Monotone_Queue_DP_Result<Cost>{std::move(dp), std::move(parent)};
725}
726
747template <typename T>
749 const Array<T> &weights)
750{
751 static_assert(std::is_integral_v<T> and std::is_signed_v<T>,
752 "min_weighted_squared_distance_1d requires signed integral T");
753
754 ah_domain_error_if(xs.size() != weights.size())
755 << "min_weighted_squared_distance_1d: xs and weights size mismatch";
756
757 const size_t n = xs.size();
758 if (n == 0)
759 return Array<T>();
760
761 T min_x = xs[0];
762 T max_x = xs[0];
763 for (size_t i = 1; i < n; ++i)
764 {
765 if (xs[i] < min_x)
766 min_x = xs[i];
767 if (xs[i] > max_x)
768 max_x = xs[i];
769 }
770
771 Li_Chao_Tree<T> lc(min_x, max_x);
773 for (size_t j = 0; j < n; ++j)
774 {
775 const auto px = static_cast<PromotedT>(xs[j]);
776 const T m = static_cast<T>(static_cast<PromotedT>(-2) * px);
777 const T b = static_cast<T>(px * px + static_cast<PromotedT>(weights[j]));
778 lc.add_line(m, b);
779 }
780
782 for (size_t i = 0; i < n; ++i)
783 {
784 const auto px = static_cast<PromotedT>(xs[i]);
785 ret(i) = static_cast<T>(px * px + static_cast<PromotedT>(lc.query(xs[i])));
786 }
787
788 return ret;
789}
790} // namespace Aleph
791
792#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:584
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:271
long double w
Definition btreepic.C:153
size_t row
Definition ca-c-api.h:115
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:196
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
void swap(Array &s) noexcept
Swap this with s
Definition tpl_array.H:232
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
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)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
std::conditional_t< std::is_floating_point_v< T >, T, std::conditional_t<(sizeof(T)< 8), long long, _promoted_int_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
DynList< T > intercept(const Container< T > &c1, const Container< T > &c2)
Return intersection of two containers as a DynList.
Array< T > min_weighted_squared_distance_1d(const Array< T > &xs, const Array< T > &weights)
Weighted squared-distance lower envelope on a line.
and
Check uniqueness with explicit hash + equality functors.
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