27#include "absl/algorithm/container.h"
28#include "absl/container/btree_set.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/inlined_vector.h"
31#include "absl/log/check.h"
32#include "absl/numeric/int128.h"
33#include "absl/random/bit_gen_ref.h"
34#include "absl/random/distributions.h"
35#include "absl/strings/str_cat.h"
36#include "absl/types/span.h"
37#include "google/protobuf/descriptor.h"
42#include "ortools/sat/sat_parameters.pb.h"
50 std::string s = absl::StrCat(num);
52 const int size = s.size();
53 for (
int i = 0;
i <
size; ++
i) {
54 if (
i > 0 && (
size -
i) % 3 == 0) {
64inline std::string LeftAlign(std::string s,
int size = 16) {
65 if (s.size() >=
size)
return s;
70inline std::string RightAlign(std::string s,
int size = 16) {
71 if (s.size() >=
size)
return s;
72 return absl::StrCat(std::string(
size - s.size(),
' '), s);
77std::string
FormatTable(std::vector<std::vector<std::string>>& table,
79 if (table.size() > 1) {
81 std::sort(table.begin() + 1, table.end());
84 std::vector<int> widths;
85 for (
const std::vector<std::string>&
line : table) {
86 if (
line.size() > widths.size()) widths.resize(
line.size(), spacing);
87 for (
int j = 0; j <
line.size(); ++j) {
88 widths[j] = std::max<int>(widths[j],
line[j].
size() + spacing);
92 for (
int i = 0;
i < table.size(); ++
i) {
93 for (
int j = 0; j < table[
i].size(); ++j) {
94 if (
i == 0 && j == 0) {
96 absl::StrAppend(&output, LeftAlign(table[
i][j], widths[j]));
98 absl::StrAppend(&output, RightAlign(table[
i][j], widths[j]));
101 absl::StrAppend(&output,
"\n");
108#if !defined(__PORTABLE_PLATFORM__)
110 const google::protobuf::EnumDescriptor* order_d =
111 SatParameters::VariableOrder_descriptor();
113 static_cast<SatParameters::VariableOrder
>(
114 order_d->value(absl::Uniform(random, 0, order_d->value_count()))
118 const google::protobuf::EnumDescriptor* polarity_d =
119 SatParameters::Polarity_descriptor();
120 parameters->set_initial_polarity(
static_cast<SatParameters::Polarity
>(
121 polarity_d->value(absl::Uniform(random, 0, polarity_d->value_count()))
125 parameters->set_use_phase_saving(absl::Bernoulli(random, 0.5));
126 parameters->set_random_polarity_ratio(absl::Bernoulli(random, 0.5) ? 0.01
128 parameters->set_random_branches_ratio(absl::Bernoulli(random, 0.5) ? 0.01
139void QuotientAndRemainder(int64_t
a, int64_t
b, int64_t& q, int64_t& r) {
153 int64_t r[2] = {m,
x};
154 int64_t t[2] = {0, 1};
167 for (; r[
i ^ 1] != 0;
i ^= 1) {
168 QuotientAndRemainder(r[
i], r[
i ^ 1], q, r[
i]);
169 t[
i] -= t[
i ^ 1] * q;
173 if (r[
i] != 1)
return 0;
177 if (t[
i] < 0) t[
i] += m;
183 const int64_t r =
x % m;
184 return r < 0 ? r + m : r;
192 if (rhs == 0 || mod == 1)
return 0;
193 DCHECK_EQ(std::gcd(std::abs(coeff), mod), 1);
202 CHECK_NE(inverse, 0);
205 const absl::int128 p = absl::int128{inverse} * absl::int128{rhs};
206 return static_cast<int64_t
>(p % absl::int128{mod});
210 int64_t& x0, int64_t& y0) {
213 CHECK_NE(
a, std::numeric_limits<int64_t>::min());
214 CHECK_NE(
b, std::numeric_limits<int64_t>::min());
216 const int64_t gcd = std::gcd(std::abs(
a), std::abs(
b));
217 if (cte % gcd != 0)
return false;
233 if (cte < 0 && x0 != 0) x0 -= std::abs(
b);
239 const absl::int128 t = absl::int128{cte} - absl::int128{
a} * absl::int128{x0};
240 DCHECK_EQ(t % absl::int128{
b}, absl::int128{0});
245 const absl::int128 r = t / absl::int128{
b};
246 DCHECK_LE(r, absl::int128{std::numeric_limits<int64_t>::max()});
247 DCHECK_GE(r, absl::int128{std::numeric_limits<int64_t>::min()});
249 y0 =
static_cast<int64_t
>(r);
258 static_cast<int64_t
>(std::floor(std::sqrt(
static_cast<double>(
a))));
259 while (
CapProd(result, result) >
a) --result;
260 while (
CapProd(result + 1, result + 1) <=
a) ++result;
267 static_cast<int64_t
>(std::ceil(std::sqrt(
static_cast<double>(
a))));
268 while (
CapProd(result, result) <
a) ++result;
269 while ((result - 1) * (result - 1) >=
a) --result;
275 int64_t result =
value / base * base;
276 if (
value - result > base / 2) result += base;
281 int64_t base, absl::Span<const int64_t> coeffs,
282 absl::Span<const int64_t> lbs, absl::Span<const int64_t> ubs, int64_t rhs,
285 int64_t max_activity = 0;
287 int64_t min_error = 0;
288 const int num_terms = coeffs.size();
289 if (num_terms == 0)
return false;
290 for (
int i = 0;
i < num_terms; ++
i) {
291 const int64_t coeff = coeffs[
i];
294 max_activity += coeff * ubs[
i];
295 max_x += closest / base * ubs[
i];
297 const int64_t error = coeff - closest;
299 min_error += error * lbs[
i];
301 min_error += error * ubs[
i];
305 if (max_activity <= rhs) {
312 int64_t max_error_if_invalid = 0;
313 const int64_t slack = max_activity - rhs - 1;
314 for (
int i = 0;
i < num_terms; ++
i) {
315 const int64_t coeff = coeffs[
i];
317 const int64_t error = coeff - closest;
319 max_error_if_invalid += error * ubs[
i];
321 const int64_t lb = std::max(lbs[
i], ubs[
i] - slack / coeff);
322 max_error_if_invalid += error * lb;
337 const int64_t infeasibility_bound =
341 return *new_rhs < infeasibility_bound;
345 const absl::btree_set<LiteralIndex>& processed,
int relevant_prefix_size,
346 std::vector<Literal>* literals) {
347 if (literals->empty())
return -1;
348 if (!processed.contains(literals->back().Index())) {
349 return std::min<int>(relevant_prefix_size, literals->size());
359 int num_processed = 0;
360 int num_not_processed = 0;
361 int target_prefix_size = literals->size() - 1;
362 for (
int i = literals->size() - 1;
i >= 0;
i--) {
363 if (processed.contains((*literals)[
i].Index())) {
367 target_prefix_size =
i;
369 if (num_not_processed >= num_processed)
break;
371 if (num_not_processed == 0)
return -1;
372 target_prefix_size = std::min(target_prefix_size, relevant_prefix_size);
376 std::stable_partition(
377 literals->begin() + target_prefix_size, literals->end(),
378 [&processed](
Literal l) { return processed.contains(l.Index()); });
379 return target_prefix_size;
383 DCHECK(!
input.empty());
385 double total_weight = 0;
386 for (
const double w :
input) {
390 const double weight_point = absl::Uniform(random, 0.0f, total_weight);
391 double total_weight2 = 0;
392 for (
int i = 0;
i <
input.size(); ++
i) {
393 total_weight2 +=
input[
i];
394 if (total_weight2 > weight_point) {
398 return input.size() - 1;
403 average_ = reset_value;
408 average_ += (new_record - average_) / num_records_;
413 average_ = (num_records_ == 1)
415 : (new_record + decaying_factor_ * (average_ - new_record));
419 records_.push_front(record);
420 if (records_.size() > record_limit_) {
426 CHECK_GT(records_.size(), 0);
427 CHECK_LE(percent, 100.0);
428 CHECK_GE(percent, 0.0);
430 const int num_records = records_.size();
431 const double percentile_rank =
432 static_cast<double>(num_records) * percent / 100.0 - 0.5;
433 if (percentile_rank <= 0) {
434 return *absl::c_min_element(records_);
435 }
else if (percentile_rank >= num_records - 1) {
436 return *absl::c_max_element(records_);
438 std::vector<double> sorted_records;
439 sorted_records.assign(records_.begin(), records_.end());
441 DCHECK_GE(num_records, 2);
442 DCHECK_LT(percentile_rank, num_records - 1);
443 const int lower_rank =
static_cast<int>(std::floor(percentile_rank));
444 DCHECK_LT(lower_rank, num_records - 1);
445 auto upper_it = sorted_records.begin() + lower_rank + 1;
449 absl::c_nth_element(sorted_records, upper_it);
450 auto lower_it = std::max_element(sorted_records.begin(), upper_it);
451 return *lower_it + (percentile_rank - lower_rank) * (*upper_it - *lower_it);
455 std::vector<std::vector<int64_t>>* tuples) {
456 if (tuples->empty())
return;
461 const int num_vars = (*tuples)[0].size();
463 std::vector<int> to_remove;
464 std::vector<int64_t> tuple_minus_var_i(num_vars - 1);
465 for (
int i = 0;
i < num_vars; ++
i) {
466 const int domain_size = domain_sizes[
i];
467 if (domain_size == 1)
continue;
468 absl::flat_hash_map<const std::vector<int64_t>, std::vector<int>>
469 masked_tuples_to_indices;
470 for (
int t = 0; t < tuples->size(); ++t) {
472 for (
int j = 0; j < num_vars; ++j) {
473 if (
i == j)
continue;
474 tuple_minus_var_i[out++] = (*tuples)[t][j];
476 masked_tuples_to_indices[tuple_minus_var_i].push_back(t);
479 for (
const auto& it : masked_tuples_to_indices) {
480 if (it.second.size() != domain_size)
continue;
482 to_remove.insert(to_remove.end(), it.second.begin() + 1, it.second.end());
484 std::sort(to_remove.begin(), to_remove.end(), std::greater<int>());
485 for (
const int t : to_remove) {
486 (*tuples)[t] = tuples->back();
496 expanded_sums_.clear();
502 if (
value == 0)
return;
503 if (
value > bound_)
return;
504 gcd_ = std::gcd(gcd_,
value);
505 AddChoicesInternal({
value});
510 for (
const int64_t c : choices) {
516 if (current_max_ == bound_)
return;
519 filtered_values_.clear();
520 for (
const int64_t c : choices) {
521 if (c == 0 || c > bound_)
continue;
522 filtered_values_.push_back(c);
523 gcd_ = std::gcd(gcd_, c);
525 if (filtered_values_.empty())
return;
528 std::sort(filtered_values_.begin(), filtered_values_.end());
529 AddChoicesInternal(filtered_values_);
534 DCHECK_GE(max_value, 0);
536 if (coeff == 0 || max_value == 0)
return;
537 if (coeff > bound_)
return;
538 if (current_max_ == bound_)
return;
539 gcd_ = std::gcd(gcd_, coeff);
541 const int64_t num_values = std::min(max_value,
FloorOfRatio(bound_, coeff));
542 if (num_values > 10) {
545 expanded_sums_.clear();
550 filtered_values_.clear();
551 for (
int multiple = 1; multiple <= num_values; ++multiple) {
552 const int64_t v = multiple * coeff;
554 current_max_ = bound_;
557 filtered_values_.push_back(v);
559 AddChoicesInternal(filtered_values_);
562void MaxBoundedSubsetSum::AddChoicesInternal(absl::Span<const int64_t> values) {
564 if (!sums_.empty() && sums_.size() <= max_complexity_per_add_) {
565 const int old_size = sums_.size();
566 for (
int i = 0;
i < old_size; ++
i) {
567 for (
const int64_t
value : values) {
568 const int64_t s = sums_[
i] +
value;
569 if (s > bound_)
break;
572 current_max_ = std::max(current_max_, s);
573 if (current_max_ == bound_)
return;
580 if (bound_ <= max_complexity_per_add_) {
581 if (!sums_.empty()) {
582 expanded_sums_.assign(bound_ + 1,
false);
583 for (
const int64_t s : sums_) {
584 expanded_sums_[s] =
true;
590 if (!expanded_sums_.empty()) {
591 for (int64_t
i = bound_ - 1;
i >= 0; --
i) {
592 if (!expanded_sums_[
i])
continue;
593 for (
const int64_t
value : values) {
594 if (
i +
value > bound_)
break;
596 expanded_sums_[
i +
value] =
true;
597 current_max_ = std::max(current_max_,
i +
value);
598 if (current_max_ == bound_)
return;
608 current_max_ = bound_;
615 if (candidate > bound_ || current_max_ == bound_)
return current_max_;
617 int64_t current_max = current_max_;
619 if (!sums_.empty()) {
620 for (
const int64_t v : sums_) {
621 if (v + candidate > bound_)
continue;
622 if (v + candidate > current_max) {
623 current_max = v + candidate;
624 if (current_max == bound_)
return current_max;
631 if (!expanded_sums_.empty()) {
632 const int64_t min_useful = std::max<int64_t>(0, current_max_ - candidate);
633 const int64_t max_useful = bound_ - candidate;
634 for (int64_t v = max_useful; v >= min_useful; --v) {
635 if (expanded_sums_[v])
return v + candidate;
642 const std::vector<Domain>& domains,
const std::vector<int64_t>& coeffs,
643 const std::vector<int64_t>& costs,
const Domain& rhs) {
644 const int num_vars = domains.size();
645 if (num_vars == 0)
return {};
647 int64_t min_activity = 0;
648 int64_t max_domain_size = 0;
649 for (
int i = 0;
i < num_vars; ++
i) {
650 max_domain_size = std::max(max_domain_size, domains[
i].Size());
652 min_activity += coeffs[
i] * domains[
i].Min();
654 min_activity += coeffs[
i] * domains[
i].Max();
663 const int64_t num_values = rhs.
Max() - min_activity + 1;
664 if (num_values < 0) {
673 const int64_t max_work_per_variable = std::min(num_values, max_domain_size);
674 if (rhs.
Max() - min_activity > 1e6)
return {};
675 if (num_vars * num_values * max_work_per_variable > 1e8)
return {};
681 for (
int i = 0;
i < num_vars; ++
i) {
683 domains_.push_back(domains[
i].AdditionWith(
Domain(-domains[
i].Min())));
684 coeffs_.push_back(coeffs[
i]);
685 costs_.push_back(costs[
i]);
688 domains[
i].Negation().AdditionWith(
Domain(domains[
i].Max())));
689 coeffs_.push_back(-coeffs[
i]);
690 costs_.push_back(-costs[
i]);
698 for (
int i = 0;
i < num_vars; ++
i) {
710 int64_t num_values,
const Domain& rhs) {
711 const int num_vars = domains_.size();
714 var_activity_states_.assign(num_vars, std::vector<State>(num_values));
717 for (
const int64_t v : domains_[0].Values()) {
718 const int64_t
value = v * coeffs_[0];
720 if (
value >= num_values)
break;
721 var_activity_states_[0][
value].cost = v * costs_[0];
722 var_activity_states_[0][
value].value = v;
726 for (
int i = 1;
i < num_vars; ++
i) {
727 const std::vector<State>& prev = var_activity_states_[
i - 1];
728 std::vector<State>& current = var_activity_states_[
i];
729 for (
int prev_value = 0; prev_value < num_values; ++prev_value) {
730 if (prev[prev_value].cost == std::numeric_limits<int64_t>::max()) {
733 for (
const int64_t v : domains_[
i].Values()) {
734 const int64_t
value = prev_value + v * coeffs_[
i];
736 if (
value >= num_values)
break;
737 const int64_t new_cost = prev[prev_value].cost + v * costs_[
i];
738 if (new_cost < current[
value].cost) {
739 current[
value].cost = new_cost;
740 current[
value].value = v;
747 result.solved =
true;
749 int64_t best_cost = std::numeric_limits<int64_t>::max();
750 int64_t best_activity;
751 for (
int v = 0; v < num_values; ++v) {
754 if (var_activity_states_.back()[v].cost < best_cost) {
755 best_cost = var_activity_states_.back()[v].cost;
760 if (best_cost == std::numeric_limits<int64_t>::max()) {
761 result.infeasible =
true;
766 result.solution.resize(num_vars);
767 int64_t current_activity = best_activity;
768 for (
int i = num_vars - 1;
i >= 0; --
i) {
769 const int64_t var_value = var_activity_states_[
i][current_activity].value;
770 result.solution[
i] = var_value;
771 current_activity -= coeffs_[
i] * var_value;
785void FullyCompressTuplesRecursive(
786 absl::Span<const int64_t> domain_sizes,
787 absl::Span<std::vector<int64_t>> tuples,
788 std::vector<absl::InlinedVector<int64_t, 2>>* reversed_suffix,
789 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>>* output) {
791 absl::InlinedVector<int64_t, 2> values;
794 bool operator<(
const TempData& other)
const {
795 return values < other.values;
798 std::vector<TempData> temp_data;
800 CHECK(!tuples.empty());
801 CHECK(!tuples[0].empty());
802 const int64_t domain_size = domain_sizes[tuples[0].size() - 1];
805 std::sort(tuples.begin(), tuples.end());
806 for (
int i = 0;
i < tuples.size();) {
808 temp_data.push_back({{tuples[
start].back()},
start});
809 tuples[
start].pop_back();
810 for (++
i;
i < tuples.size(); ++
i) {
811 const int64_t v = tuples[
i].back();
812 tuples[
i].pop_back();
813 if (tuples[
i] == tuples[
start]) {
814 temp_data.back().values.push_back(v);
816 tuples[
i].push_back(v);
823 for (
const int64_t v : temp_data.back().values) {
825 temp_data.back().values.clear();
833 if (temp_data.back().values.size() == domain_size) {
834 temp_data.back().values.clear();
838 if (temp_data.size() == 1) {
839 output->push_back({});
840 for (
const int64_t v : tuples[temp_data[0].
index]) {
842 output->back().push_back({});
844 output->back().push_back({v});
847 output->back().push_back(temp_data[0].values);
848 for (
int i = reversed_suffix->size(); --
i >= 0;) {
849 output->back().push_back((*reversed_suffix)[
i]);
856 std::sort(temp_data.begin(), temp_data.end());
857 std::vector<std::vector<int64_t>> temp_tuples;
858 for (
int i = 0;
i < temp_data.size();) {
859 reversed_suffix->push_back(temp_data[
i].values);
862 for (;
i < temp_data.size();
i++) {
863 if (temp_data[
start].values != temp_data[
i].values)
break;
864 temp_tuples.push_back(tuples[temp_data[
i].
index]);
866 FullyCompressTuplesRecursive(domain_sizes, absl::MakeSpan(temp_tuples),
867 reversed_suffix, output);
868 reversed_suffix->pop_back();
879 absl::Span<const int64_t> domain_sizes,
880 std::vector<std::vector<int64_t>>* tuples) {
881 std::vector<absl::InlinedVector<int64_t, 2>> reversed_suffix;
882 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> output;
883 FullyCompressTuplesRecursive(domain_sizes, absl::MakeSpan(*tuples),
884 &reversed_suffix, &output);
890class CliqueDecomposition {
892 CliqueDecomposition(
const std::vector<std::vector<int>>&
graph,
893 absl::BitGenRef random, std::vector<int>* buffer)
894 : graph_(
graph), random_(random), buffer_(buffer) {
895 const int n =
graph.size();
896 permutation_.resize(n);
900 for (
int i = 0;
i < n; ++
i) permutation_[
i] =
i;
907 void DecomposeGreedily() {
908 decomposition_.clear();
911 const int n = graph_.size();
912 taken_.assign(n,
false);
913 temp_.assign(n,
false);
915 int buffer_index = 0;
916 for (
const int i : permutation_) {
917 if (taken_[
i])
continue;
920 const int start = buffer_index;
922 (*buffer_)[buffer_index++] =
i;
925 for (
const int c : graph_[
i]) {
926 if (!taken_[c]) candidates_.push_back(c);
928 while (!candidates_.empty()) {
930 int next = candidates_.front();
931 for (
const int n : candidates_) {
932 if (permutation_[n] < permutation_[
next])
next = n;
937 (*buffer_)[buffer_index++] =
next;
943 for (
const int c : candidates_) {
944 if (taken_[c])
continue;
945 if (!temp_[c])
continue;
946 candidates_[new_size++] =
c;
948 candidates_.resize(new_size);
949 for (
const int head : graph_[
next]) temp_[
head] =
false;
953 decomposition_.push_back(
954 absl::MakeSpan(buffer_->data() +
start, buffer_index -
start));
956 DCHECK_EQ(buffer_index, n);
966 if (absl::Bernoulli(random_, 0.5)) {
967 std::reverse(decomposition_.begin(), decomposition_.end());
969 std::shuffle(decomposition_.begin(), decomposition_.end(), random_);
973 for (
const absl::Span<const int> clique : decomposition_) {
974 for (
const int i : clique) {
975 permutation_[out_index++] =
i;
980 const std::vector<absl::Span<int>>& decomposition()
const {
981 return decomposition_;
985 const std::vector<std::vector<int>>& graph_;
986 absl::BitGenRef random_;
987 std::vector<int>* buffer_;
989 std::vector<absl::Span<int>> decomposition_;
990 std::vector<int> candidates_;
991 std::vector<int> permutation_;
992 std::vector<bool> taken_;
993 std::vector<bool> temp_;
999 const std::vector<std::vector<int>>&
graph, absl::BitGenRef random,
1000 std::vector<int>* buffer) {
1001 CliqueDecomposition decomposer(
graph, random, buffer);
1002 for (
int pass = 0; pass < 10; ++pass) {
1003 decomposer.DecomposeGreedily();
1004 if (decomposer.decomposition().size() == 1)
break;
1005 decomposer.ChangeOrder();
1007 return decomposer.decomposition();
bool Contains(int64_t value) const
Domain AdditionWith(const Domain &domain) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
Result Solve(const std::vector< Domain > &domains, const std::vector< int64_t > &coeffs, const std::vector< int64_t > &costs, const Domain &rhs)
void AddData(double new_record)
void Reset(double reset_value)
Sets the number of records to 0 and average to 'reset_value'.
void AddData(double new_record)
void AddChoices(absl::Span< const int64_t > choices)
int64_t MaxIfAdded(int64_t candidate) const
Returns the updated max if value was added to the subset-sum.
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
void Reset(int64_t bound)
void AddMultiples(int64_t coeff, int64_t max_value)
Adds [0, coeff, 2 * coeff, ... max_value * coeff].
void AddRecord(double record)
double GetPercentile(double percent)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
constexpr int64_t kTableAnyValue
void CompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Randomizes the decision heuristic of the given SatParameters.
int64_t ProductWithModularInverse(int64_t coeff, int64_t mod, int64_t rhs)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
std::vector< std::vector< absl::InlinedVector< int64_t, 2 > > > FullyCompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
int64_t CeilSquareRoot(int64_t a)
int64_t ModularInverse(int64_t x, int64_t m)
IntType FloorOfRatio(IntType numerator, IntType denominator)
int WeightedPick(absl::Span< const double > input, absl::BitGenRef random)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
int64_t ClosestMultiple(int64_t value, int64_t base)
int64_t PositiveMod(int64_t x, int64_t m)
Just returns x % m but with a result always in [0, m).
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, absl::Span< const int64_t > coeffs, absl::Span< const int64_t > lbs, absl::Span< const int64_t > ubs, int64_t rhs, int64_t *new_rhs)
int MoveOneUnprocessedLiteralLast(const absl::btree_set< LiteralIndex > &processed, int relevant_prefix_size, std::vector< Literal > *literals)
std::vector< absl::Span< int > > AtMostOneDecomposition(const std::vector< std::vector< int > > &graph, absl::BitGenRef random, std::vector< int > *buffer)
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapProd(int64_t x, int64_t y)
trees with all degrees equal w the current value of w
static int input(yyscan_t yyscanner)
std::vector< int64_t > solution