Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
n_choose_k.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cmath>
17#include <cstdint>
18#include <limits>
19#include <vector>
20
21#include "absl/log/check.h"
22#include "absl/numeric/int128.h"
23#include "absl/status/status.h"
24#include "absl/status/statusor.h"
25#include "absl/strings/str_format.h"
26#include "absl/time/clock.h"
27#include "absl/time/time.h"
31
32namespace operations_research {
33namespace {
34// This is the actual computation. It's in O(k).
35template <typename Int>
36Int InternalChoose(Int n, Int k) {
37 DCHECK_LE(k, n - k);
38 // We compute n * (n-1) * ... * (n-k+1) / k! in the best possible order to
39 // guarantee exact results, while trying to avoid overflows. It's not perfect:
40 // we finish with a division by k, which means that me may overflow even if
41 // the result doesn't (by a factor of up to k).
42 Int result = 1;
43 Int i = 0;
44 while (i < k) { // We always have k < n/2.
45 result *= n--;
46 result /= ++i; // The product of i consecutive numbers is divisible by i.
47 }
48 return result;
49}
50
51constexpr int64_t kint64max = std::numeric_limits<int64_t>::max();
52
53// This function precomputes the maximum N such that (N choose K) doesn't
54// overflow, for all K.
55// When `overflows_intermediate_computation` is true, "overflow" means
56// "some overflow happens inside InternalChoose<int64_t>()", and when it's false
57// it simply means "the result doesn't fit in an int64_t".
58// This is only used in contexts where K ≤ N-K, which implies N ≥ 2K, thus we
59// can stop when (2K Choose K) overflows, because at and beyond such K,
60// (N Choose K) will always overflow. In practice that happens for K=31 or 34
61// depending on `overflows_intermediate_computation`.
62std::vector<int64_t> LastNThatDoesNotOverflowForAllK(
63 bool overflows_intermediate_computation) {
64 absl::Time start_time = absl::Now();
65 // Given the algorithm used in InternalChoose(), it's not hard to
66 // find out when (N choose K) overflows an int64_t during its internal
67 // computation: that's when (N choose K) > kint64max / k.
68
69 // For K ≤ 2, we hardcode the values of the maximum N.
70 std::vector<int64_t> result = {
71 kint64max, // K=0
72 kint64max, // K=1
73 // The binary search done below uses MathUtil::LogCombinations, which only
74 // works on int32_t, and that's problematic for the max N we get for K=2.
75 overflows_intermediate_computation
76 ? // Max N such that N*(N-1) < 2^63. N*(N-1) ≈ (N-0.5)².
77 static_cast<int64_t>(0.5 + std::pow(2.0, 63.0 / 2))
78 : 1l << 32, // Max N such that N*(N-1) < 2^64.
79 };
80 // We find the last N with binary search, for all K. We stop growing K
81 // when (2*K Choose K) overflows.
82 for (int64_t k = 3;; ++k) {
83 const double max_log_comb = overflows_intermediate_computation
84 ? 63 * std::log(2) - std::log(k)
85 : 63 * std::log(2);
86 result.push_back(BinarySearch<int64_t>(
87 /*x_true*/ k, /*x_false=*/(1l << 23) - 1, [k, max_log_comb](int64_t n) {
88 return MathUtil::LogCombinations(n, k) <= max_log_comb;
89 }));
90 if (result.back() < 2 * k) {
91 result.pop_back();
92 break;
93 }
94 }
95 DCHECK_EQ(result.size(),
96 overflows_intermediate_computation
97 ? 31 // 60 Choose 30 < 2^63/30 but 62 Choose 31 > 2^63/31.
98 : 34); // 66 Choose 33 < 2^63 but 68 Choose 34 > 2^63.
99 VLOG(1) << "LastNThatDoesNotOverflowForAllK(): " << absl::Now() - start_time;
100 return result;
101}
102
103bool NChooseKIntermediateComputationOverflowsInt64(int64_t n, int64_t k) {
104 DCHECK_LE(k, n - k);
105 static const auto* const result =
106 new std::vector<int64_t>(LastNThatDoesNotOverflowForAllK(
107 /*overflows_intermediate_computation=*/true));
108 return k < result->size() ? n > (*result)[k] : true;
109}
110
111bool NChooseKResultOverflowsInt64(int64_t n, int64_t k) {
112 DCHECK_LE(k, n - k);
113 static const auto* const result =
114 new std::vector<int64_t>(LastNThatDoesNotOverflowForAllK(
115 /*overflows_intermediate_computation=*/false));
116 return k < result->size() ? n > (*result)[k] : true;
117}
118} // namespace
119
120absl::StatusOr<int64_t> NChooseK(int64_t n, int64_t k) {
121 if (n < 0) {
122 return absl::InvalidArgumentError(absl::StrFormat("n is negative (%d)", n));
123 }
124 if (k < 0) {
125 return absl::InvalidArgumentError(absl::StrFormat("k is negative (%d)", k));
126 }
127 if (k > n) {
128 return absl::InvalidArgumentError(
129 absl::StrFormat("k=%d is greater than n=%d", k, n));
130 }
131 // NOTE(user): If performance ever matters, we could simply precompute and
132 // store all (N choose K) that don't overflow, there aren't that many of them:
133 // only a few tens of thousands, after removing simple cases like k ≤ 5.
134 if (k > n / 2) k = n - k;
135 if (!NChooseKIntermediateComputationOverflowsInt64(n, k)) {
136 return InternalChoose<int64_t>(n, k);
137 }
138 if (NChooseKResultOverflowsInt64(n, k)) {
139 return absl::InvalidArgumentError(
140 absl::StrFormat("(%d choose %d) overflows int64", n, k));
141 }
142 return static_cast<int64_t>(InternalChoose<absl::int128>(n, k));
143}
144
145} // namespace operations_research
static double LogCombinations(int n, int k)
Definition mathutil.cc:33
In SWIG mode, we don't want anything besides these top-level includes.
absl::StatusOr< int64_t > NChooseK(int64_t n, int64_t k)
Point BinarySearch(Point x_true, Point x_false, std::function< bool(Point)> f)
STL namespace.
static const int64_t kint64max
Definition types.h:32