C ++, làm nổ tung tất cả bộ nhớ trên máy tính gần bạn
Tạo chuỗi ngắn nhất trong đó tính toán không gây ra tràn số nguyên 32 bit đã ký (vì vậy tất cả các kết quả trung gian đều nằm trong phạm vi [-2147483648, 2147483647]
Trên hệ thống của tôi, điều này tạo ra một giải pháp cho tất cả các số lên đến và bao gồm 483432
trong 30 giây trong khi sử dụng bộ nhớ 1.8G. Số thậm chí cao hơn sẽ nhanh chóng bùng nổ việc sử dụng bộ nhớ. Số lượng cao nhất tôi có thể xử lý trên hệ thống của tôi là 5113906
. Việc tính toán mất gần 9 phút và 24GB. Khi hoàn thành, bên trong nó có một giải pháp cho 398499338
các giá trị, khoảng 9% của tất cả các số nguyên 32 bit (dương và âm)
Cần một trình biên dịch C ++ 11. Trên linux biên dịch với:
g++ -Wall -O3 -march=native -std=gnu++11 -s befour.cpp -o befour
Thêm -DINT64
dưới dạng tùy chọn để sử dụng phạm vi số nguyên 64 bit thay vì 32 bit cho kết quả trung gian (điều này sẽ sử dụng thêm khoảng 50% thời gian và bộ nhớ). Điều này cần một loại 128 bit dựng sẵn. Bạn có thể cần phải thay đổi loại gcc __int128
. Không có kết quả trong ít nhất phạm vi [1..483432]
thay đổi bằng cách cho phép kết quả trung gian lớn hơn.
Thêm -DOVERFLOW
dưới dạng tùy chọn để không sử dụng loại số nguyên lớn hơn để kiểm tra tràn. Điều này có tác dụng cho phép tràn và giá trị gói.
Nếu hệ thống của bạn có tcmalloc ( https://github.com/gperftools/gperftools ), bạn có thể liên kết với chương trình đó thường nhanh hơn một chút và sử dụng ít bộ nhớ hơn. Trên một số hệ thống UNIX, bạn có thể sử dụng tải trước, ví dụ:
LD_PRELOAD=/usr/lib/libtcmalloc_minimal.so.4 befour 5
Sử dụng cơ bản: tạo và in tất cả các số theo mục tiêu:
befour target
Tùy chọn:
-a
Đồng thời in tất cả các số được tạo trong khi thực hiện mục tiêu
-c
Đồng thời in tất cả các số được tạo bắt đầu bằng "carry" (dup)
-f
Tìm và in số đầu tiên ngoài mục tiêu không được tạo
-s
Dừng nếu mục tiêu được tạo ngay cả khi không phải tất cả các số trước đó đã được tạo
-S
Thích -s
và-f
trong một vòng lặp tự động. Ngay khi mục tiêu được tạo, hãy tìm số đầu tiên chưa được tạo và làm cho mục tiêu mới đó
-E
Đừng thoát ngay lập tức khi đạt được mục tiêu. Đầu tiên kết thúc tất cả các chuỗi có độ dài hiện tại
-O
Không xuất chuỗi cho tất cả các số theo mục tiêu. chỉ chuỗi cho mục tiêu
-o
Hướng dẫn được phép (mặc định là +-*/:
-b num
Nghĩa đen thấp nhất có thể được đẩy (mặc định là 0
)
-B num
Nghĩa đen cao nhất có thể được đẩy (mặc định 9
)
-r num
Kết quả trung gian cho phép thấp nhất. Được sử dụng để tránh tràn. (mặc định là INT32_MIN
,-2147483648
-R num
Kết quả trung gian cho phép cao nhất. Được sử dụng để tránh tràn. (mặc định là INT32_MAX
,2147483647
-m memory
(chỉ dành cho linux) thoát khi khoảng bộ nhớ thêm này đã được phân bổ
Một số kết hợp tùy chọn thú vị:
Tạo tất cả các số theo mục tiêu và tính toán số nhỏ nhất cần bộ tạo dài hơn tất cả các số này:
befour -fE target
Chỉ tạo mục tiêu (-s), chỉ in mục tiêu (-O)
befour -sO target
Tìm số cao nhất có thể được tạo trên hệ thống của bạn theo thời gian và / hoặc các ràng buộc về bộ nhớ (Điều này sẽ khiến hệ thống của bạn hết bộ nhớ nếu bạn để nó chạy. Trừ 1 từ đầu ra "tìm kiếm" cuối cùng mà bạn xem là giá trị an toàn cuối cùng ):
befour -S 1
Tạo giải pháp mà không bao giờ sử dụng kết quả trung gian âm ( 30932
là giá trị đầu tiên cần kết quả trung gian âm cho chuỗi ngắn nhất):
befour -r0 target
Tạo các giải pháp mà không bao giờ thúc đẩy 0
(điều này dường như không dẫn đến bất kỳ giải pháp tối ưu nào):
befour -b1 target
Tạo các giải pháp bao gồm a..f (10..15)
:
befour -B15 target
Tạo giải pháp mà không sử dụng dup :
(thêm -r0
vì giá trị trung gian âm không bao giờ thú vị trong trường hợp này)
befour -r0 -o "+-*/" target
Tìm giá trị đầu tiên mà không thể được tạo ra cho một chuỗi dài cho chỉ sử dụng +
, -
, *
và /
:
befour -ES -r0 -o "+-*/" 1
Trên thực tế, điều này sẽ tạo ra một số thuật ngữ đầu tiên của https://oeis.org/A181898 , nhưng sẽ bắt đầu phân kỳ 14771
vì chúng tôi sử dụng phép chia cắt để số đó có thể được thực hiện với chuỗi dài 13 thay vì dài 15 như chuỗi OEIS mong đợi:
14771: 13: 99*9*9*4+9*4/
thay vì
14771: 15: 19+5*6*7*9+7*8+
Vì không có phân chia cắt ngắn dường như vô nghĩa, chuỗi OEIS có thể được tạo tốt hơn bằng cách sử dụng
befour -ES -r0 -o"+-*" 1
Giả sử bộ phận vẫn vô dụng, điều này đã cho tôi thêm 3 điều khoản trước khi tôi hết bộ nhớ:
10, 19, 92, 417, 851, 4237, 14771, 73237, 298609, 1346341, 6176426, 25622578
Một phiên bản khác của chương trình này lưu trữ một phần dữ liệu trong các tệp bên ngoài thêm 135153107 và 675854293 sau đó tất cả các số nguyên 32 bit đã được tạo.
befour.cpp
/*
Compile using something like:
g++ -Wall -O3 -march=native -std=gnu++11 -s befour.cpp -o befour
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <limits>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <chrono>
#include <unordered_map>
using namespace std;
#ifdef __GNUC__
# define HOT __attribute__((__hot__))
# define COLD __attribute__((__cold__))
# define NOINLINE __attribute__((__noinline__))
# define LIKELY(x) __builtin_expect(!!(x),1)
# define UNLIKELY(x) __builtin_expect(!!(x),0)
#else // __GNUC__
# define HOT
# define COLD
# define NOINLINE
# define LIKELY(x) (x)
# define UNLIKELY(x) (x)
#endif // __GNUC__
#ifdef INT64
using Int = int64_t; // Supported value type
# ifndef OVERFLOW
using Int2 = __int128; // Do calculations in this type. Check overflow
# endif // OVERFLOW
#else // INT64
using Int = int32_t; // Supported value type
# ifndef OVERFLOW
using Int2 = int64_t; // Do calculations in this type. Check overflow
# endif // OVERFLOW
#endif // INT64
#ifdef OVERFLOW
using Int2 = Int;
#endif // OVERFLOW
// Supported value range
Int2 MIN = numeric_limits<Int>::lowest();
Int2 MAX = numeric_limits<Int>::max();
Int HALF_MIN, HALF_MAX;
// The initial values we can push
Int ATOM_MIN = 0;
Int ATOM_MAX = 9;
bool all = false; // Output all reached values
bool all_carry = false; // Output all values reachable using carry
bool early_exit = true; // Exit before finishing level if goal reached
bool find_hole = false; // Look for first unconstructed > target
bool output = true; // Output [1..target] instead of just target
bool single = false; // Only go for target instead of [1..target]
bool explore = false; // Don't stop, increase N until out of memory
bool do_dup = false; // Use operator :
bool do_multiply= false; // Use operator *
bool do_add = false; // Use operator +
bool do_subtract= false; // Use operator -
bool do_divide = false; // Use operator /
char const* operators = "+-*/:"; // Use these operators
size_t max_mem = SIZE_MAX; // Stop if target memory reached
size_t const MEM_CHECK = 1000000;
chrono::steady_clock::time_point start;
NOINLINE size_t get_memory(bool set_base_mem = false) {
static size_t base_mem = 0;
size_t const PAGE_SIZE = 4096;
// Linux specific. Won't hurt on other systems, just gets no result
size_t mem = 0;
std::ifstream statm;
statm.open("/proc/self/statm");
statm >> mem;
mem *= PAGE_SIZE;
if (set_base_mem) base_mem = mem;
else mem -= base_mem;
return mem;
}
// Handle commandline options.
// Simplified getopt for systems that don't have it in their library (Windows..)
class GetOpt {
private:
string const options;
char const* const* argv;
int nextchar = 0;
int optind = 1;
char ch = '?';
char const* optarg = nullptr;
public:
int ind() const { return optind; }
char const* arg() const { return optarg; }
char option() const { return ch; }
GetOpt(string const options_, char const* const* argv_) :
options(options_), argv(argv_) {}
char next() {
while (1) {
if (nextchar == 0) {
if (!argv[optind] ||
argv[optind][0] != '-' ||
argv[optind][1] == 0) return ch = 0;
if (argv[optind][1] == '-' && argv[optind][2] == 0) {
++optind;
return ch = 0;
}
nextchar = 1;
}
ch = argv[optind][nextchar++];
if (ch == 0) {
++optind;
nextchar = 0;
continue;
}
auto pos = options.find(ch);
if (pos == string::npos) ch = '?';
else if (options[pos+1] == ':') {
if (argv[optind][nextchar]) {
optarg = &argv[optind][nextchar];
} else {
optarg = argv[++optind];
if (!optarg) return ch = options[0] == ':' ? ':' : '?';
}
++optind;
nextchar = 0;
}
return ch;
}
}
};
using ms = chrono::milliseconds;
Int missing, N;
size_t cached, cached_next;
uint8_t const CARRY_MASK = '\x80';
uint8_t const LITERAL = 0;
struct How {
// Describes how to construct a number
Int left;
Int right;
uint8_t ops, op;
How(uint8_t ops_, uint8_t op_, Int carry_=0, Int left_=0, Int right_=0) :
left(left_),
right(right_),
ops(ops_),
op(carry_ ? CARRY_MASK | op_ : op_)
{}
How() = default;
How(How&&) = default;
How& operator=(How&&) = default;
static How const* predict(Int carry, Int value, int& ops);
static void print_predicted(ostream& out, Int carry, Int value, How const* Value = nullptr);
void print(ostream& out, Int carry = 0, bool length = false) const;
};
ostream& operator<<(ostream& out, How const& how) {
how.print(out, 0, true);
return out;
}
using NumSet = vector<Int>;
using NumSets = vector<NumSet>;
struct Known: public unordered_map<Int, How>
{
void store(NumSet& L, Int accu, uint8_t ops, uint8_t op,
Int left=0, Int carry_right=0, Int right=0) {
++cached;
emplace(accu, How(ops, op, carry_right, left, right));
// operator[](accu) = How(ops, op, carry_right, left, right);
L.emplace_back(accu);
}
void maybe_store(Known const& known0, NumSet& L,
Int accu, uint8_t ops, uint8_t op,
Int carry_left, Int left, Int carry_right, Int right) {
if (count(accu)) return;
if (carry_left) {
auto found = known0.find(accu);
// If we can do as good or better without carry use that
if (found != known0.end() && found->second.ops <= ops) return;
}
store(L, accu, ops, op, left, carry_right, right);
if (carry_left) return;
if (single) {
if (UNLIKELY(accu == N)) known0.maybe_explore();
} else if (1 <= accu && accu <= N) --missing;
}
NOINLINE void maybe_explore() const COLD {
--missing;
if (explore && early_exit) do_explore();
}
NOINLINE void do_explore() const COLD {
auto i = N;
while (i < MAX && count(++i));
auto end = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<ms>(end-start).count();
cerr << "Found " << N << " at " << elapsed / 1000. << " s";
auto mem = get_memory();
if (mem) cerr << " (" << mem / 1000 / 1000. << " MB)";
if (i < MAX || !count(i)) {
cerr << ", now looking for " << i << endl;
N = i;
++missing;
} else
cerr << ", every value has now been generated" << endl;
}
};
struct KnowHow {
// Describes all numbers we know how to construct
NumSets num_sets;
Known known;
KnowHow() = default;
~KnowHow() = default;
KnowHow(KnowHow const&) = delete;
KnowHow& operator=(KnowHow const&) = delete;
};
// Describes all numbers we know how to construct for a given carry
// Key 0 is special: the numbers we can construct without carry (the solutions)
unordered_map<Int, KnowHow> known_how;
// Try to predict if a subtree is a delayed How and avoid descending
// into it (since it may not exist yet)
How const* How::predict(Int carry, Int value, int& ops) {
How* Value;
if (carry) {
if (value == carry) {
Value = nullptr;
ops = 0;
} else {
Value = &known_how.at(carry).known.at(value);
ops = Value->ops;
}
} else {
if (ATOM_MIN <= value && value <= ATOM_MAX) {
Value = nullptr;
ops = 0;
} else {
Value = &known_how.at(0).known.at(value);
ops = Value->ops;
}
}
return Value;
}
void How::print_predicted(ostream& out, Int carry, Int value, How const* Value) {
if (Value) Value->print(out, carry);
else if (carry) out << ":";
else if (value > 9) out << static_cast<char>(value-10+'a');
else out << value;
}
void How::print(ostream& out, Int carry_left, bool length) const {
if (length) out << 2*ops+1 << ": ";
Int carry_right = 0;
auto op_ = op;
switch(op_) {
case LITERAL:
How::print_predicted(out, 0, left);
break;
case '*' | CARRY_MASK:
case '/' | CARRY_MASK:
case '+' | CARRY_MASK:
case '-' | CARRY_MASK:
carry_right = left;
op_ &= ~CARRY_MASK;
// Intentional drop through
case '*':
case '/':
case '+':
case '-':
{
int left_ops, right_ops;
auto Left = How::predict(carry_left, left, left_ops);
// Int right = 0;
auto Right = How::predict(carry_right, right, right_ops);
// Sanity check: tree = left_tree + root + right_tree
if (ops != left_ops + right_ops +1) {
char buffer[80];
snprintf(buffer, sizeof(buffer),
"Broken number %d %c %d, length %d != %d + %d + 1",
static_cast<int>(left), op_, static_cast<int>(right),
ops, left_ops, right_ops);
throw(logic_error(buffer));
}
How::print_predicted(out, carry_left, left, Left);
How::print_predicted(out, carry_right, right, Right);
}
// Intentional drop through
case ':':
out << op_;
break;
default:
throw(logic_error("Unknown op " + string{static_cast<char>(op_)}));
break;
}
}
// carryX indicates Xv was reached using carry. If not we also know [L, known] is known_how[0]
// carryY indicates Y was reached using carry (carryY == Xv if so)
void combine(NumSet& L, Known& known, Known const& known0, int ops, Int carryX, Int2 Xv, Int carryY, NumSet const&Y) HOT;
void combine(NumSet& L, Known& known, Known const& known0, int ops, Int carryX, Int2 Xv, Int carryY, NumSet const&Y) {
for (Int Yv: Y) {
// Yv == 0 can never lead to an optimal calculation
if (Yv == 0) continue;
Int2 accu;
if (do_multiply) {
accu = Xv * Yv;
if (accu <= MAX && accu >= MIN)
known.maybe_store(known0, L, accu, ops, '*', carryX, Xv, carryY, Yv);
}
if (do_add) {
accu = Xv + Yv;
if (accu <= MAX && accu >= MIN)
known.maybe_store(known0, L, accu, ops, '+', carryX, Xv, carryY, Yv);
}
if (do_subtract) {
accu = Xv - Yv;
if (accu <= MAX && accu >= MIN)
known.maybe_store(known0, L, accu, ops, '-', carryX, Xv, carryY, Yv);
}
if (do_divide) {
accu = Xv / Yv;
if (accu <= MAX && accu >= MIN)
known.maybe_store(known0, L, accu, ops, '/', carryX, Xv, carryY, Yv);
}
}
}
// value was constructed using a carry if and only if value != 0
NumSet const& level(KnowHow& known_how0, Int value, int ops) HOT;
NumSet const& level(KnowHow& known_how0, Int value, int ops) {
auto& from_value = known_how[value];
if (from_value.num_sets.size() <= static_cast<size_t>(ops)) {
auto& known = from_value.known;
if (from_value.num_sets.size() != static_cast<size_t>(ops)) {
if (value == 0 || ops != 1)
throw(logic_error("Unexpected level skip"));
// This was because of delayed carry creation.
// The delay is over. Create the base case
from_value.num_sets.resize(ops+1);
known.store(from_value.num_sets[0], value, 0, ':', value);
} else
from_value.num_sets.resize(ops+1);
auto& L = from_value.num_sets[ops];
if (ops == 0) {
if (value) {
known.store(L, value, ops, ':', value);
} else {
for (auto i = ATOM_MIN; i <= ATOM_MAX; ++i) {
if (single) {
if (i == N) --missing;
} else {
if (0 < i && i <= N) --missing;
}
known.store(L, i, 0, LITERAL, i);
}
}
} else {
auto& known0 = known_how0.known;
// for (auto k=ops-1; k>=0; --k) {
for (auto k=0; k<ops; ++k) {
auto const& X = from_value.num_sets[ops-1-k];
auto const& Y = known_how0.num_sets[k];
for (Int Xv: X) {
// Plain combine must come before carry combine so a plain
// solution will prune a same length carry solution
combine(L, known, known0, ops, value, Xv, 0, Y);
if (!missing && early_exit) goto DONE;
if (do_dup && (Xv > ATOM_MAX || Xv < ATOM_MIN)) {
// Dup Xv, construct something using k operators, combine
if (k == 0 && Xv != 0) {
// Delay creation of carry known_how[Xv] for 1 level
// This is purely a memory and speed optimization
// Subtraction gives 0 which is never optimal
// Division gives 1 which is never optimal
// Multiplication gives Xv ** 2
// Could be == Xv if Xv== 0 or Xv == 1, but will be
// pruned by atom - atom or atom / atom
Int2 accu = Xv;
accu *= accu;
if (accu <= MAX && accu >= MIN) {
known.maybe_store(known0, L, accu, ops, '*',
value, Xv, Xv, Xv);
}
// Addition gives Xv * 2 (!= Xv)
if (HALF_MIN <= Xv && Xv <= HALF_MAX)
known.maybe_store(known0, L, 2*Xv, ops, '+',
value, Xv, Xv, Xv);
} else {
auto& Z = level(known_how0, Xv, k);
combine(L, known, known0, ops, value, Xv, Xv, Z);
}
if (!missing && early_exit) goto DONE;
}
if (max_mem != SIZE_MAX && cached > cached_next) {
cached_next = cached + MEM_CHECK;
if (get_memory() >= max_mem) goto DONE;
}
}
}
}
// L.shrink_to_fit();
}
DONE:
return from_value.num_sets[ops];
}
void my_main(int argc, char const* const* argv) {
GetOpt options("acfm:sSEOo:b:B:r:R:", argv);
while (options.next())
switch (options.option()) {
case 'a': all = true; break;
case 'b': {
auto tmp = atoll(options.arg());
ATOM_MIN = static_cast<Int>(tmp);
if (static_cast<long long int>(ATOM_MIN) != tmp)
throw(range_error("ATOM_MIN is out of range"));
break;
}
case 'B': {
auto tmp = atoll(options.arg());
ATOM_MAX = static_cast<Int>(tmp);
if (static_cast<long long int>(ATOM_MAX) != tmp)
throw(range_error("ATOM_MAX is out of range"));
break;
}
case 'c': all_carry = true; break;
case 'f': find_hole = true; break;
case 'm': max_mem = atoll(options.arg()); break;
case 'S': explore = true; // intended drop through to single
case 's': single = true; break;
case 'o': operators = options.arg(); break;
case 'E': early_exit = false; break;
case 'r': {
auto tmp = atoll(options.arg());
MIN = static_cast<Int>(tmp);
if (static_cast<long long int>(MIN) != tmp)
throw(range_error("MIN is out of range"));
break;
}
case 'R': {
auto tmp = atoll(options.arg());
MAX = static_cast<Int>(tmp);
if (static_cast<long long int>(MAX) != tmp)
throw(range_error("MAX is out of range"));
break;
}
case 'O': output = false; break;
default:
cerr << "usage: " << argv[0] << " [-a] [-c] [-f] [-D] [-E] [-O] [-s] [-b atom_min] [-B atom_max] [r range_min] [-R range_max] [-m max_mem] [max]" << endl;
exit(EXIT_FAILURE);
}
// Avoid silly option combinations
if (MIN > MAX) throw(logic_error("MIN above MAX"));
if (ATOM_MIN > ATOM_MAX) throw(logic_error("ATOM_MIN above ATOM_MAX"));
if (ATOM_MIN < 0) throw(range_error("Cannot represent negative atoms"));
if (ATOM_MAX > 35) throw(range_error("Cannot represent atoms > 35"));
if (ATOM_MIN < MIN) throw(range_error("ATOM_MIN is out of range"));
if (ATOM_MAX > MAX) throw(range_error("ATOM_MAX is out of range"));
HALF_MIN = MIN / 2;
HALF_MAX = MAX / 2;
for (auto ops=operators; *ops; ++ops)
switch(*ops) {
case '*': do_multiply = true; break;
case '/': do_divide = true; break;
case '+': do_add = true; break;
case '-': do_subtract = true; break;
case ':': do_dup = true; break;
default:
throw(logic_error("Unknown operator"));
}
long long int const NN =
options.ind() < argc ? atoll(argv[options.ind()]) : 1;
if (NN < MIN || NN > MAX)
throw(range_error("Target number is out of range"));
N = NN;
if (N < 1) {
single = true;
output = false;
}
cerr << "N=" << N << ", using " << sizeof(Int) * CHAR_BIT << " bits without overflow" << endl;
missing = single ? 1 : N;
cached = cached_next = 0;
auto& known_how0 = known_how[0];
auto& known = known_how0.known;
auto mem = get_memory(true);
if (!mem && max_mem != SIZE_MAX)
throw(runtime_error("Cannot get memory usage on this system"));
// Start calculation
start = chrono::steady_clock::now();
// Fill in initial values [0..9]
level(known_how0, 0, 0);
// Grow number of allowed operations until all requested numbers are reached
// for (auto ops=1; ops <=5; ++ops) {
for (auto ops=1;;++ops) {
if (missing == 0) {
if (!explore) break;
known_how0.known.do_explore();
if (missing == 0) break;
}
if (max_mem != SIZE_MAX && get_memory() >= max_mem) break;
auto end = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<ms>(end-start).count();
cerr << "Reaching for " << 2*ops+1 << " instructions at " << elapsed/1000. << " s";
if (mem) cerr << " (" << get_memory() / 1000 / 1000. << " MB)";
cerr << endl;
auto old_cached = cached;
level(known_how0, 0, ops);
if (cached == old_cached) {
cerr << "Oops, all possible numbers have been generated and we still weren't finished" << endl;
break;
}
}
// We are done generating all numbers.
auto end = chrono::steady_clock::now();
// Report the result
// length = 2*ops + 1
Int limit = known_how0.num_sets.size()*2-1;
cerr << "Some numbers needed " << limit << " instructions" << endl;
auto elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
stringstream out;
out << "Calculation: " << elapsed/1000. << " s\n";
for (auto i = output ? 1 : N; i <= N; ++i) {
if (single || missing) {
auto got = known.find(i);
if (got != known.end())
cout << i << ": " << got->second << "\n";
else
cout << i << " not generated\n";
} else
cout << i << ": " << known.at(i) << "\n";
}
if (output) {
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "Printing: " << elapsed/1000. << " s\n";
}
if (find_hole) {
Int hole;
for (auto i = single ? 1 : N+1; 1; ++i) {
if (!known_how0.known.count(i) || i == 0) {
hole = i;
break;
}
}
out << "First missing value " << hole << "\n";
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "Missing: " << elapsed/1000. << " s\n";
}
if (all) {
for (auto const& entry: known_how0.known) {
cout << entry.first << ": " << entry.second << "\n";
}
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "All: " << elapsed/1000. << " s\n";
}
if (all_carry) {
for (auto const& carry: known_how) {
auto carry_left = carry.first;
if (carry_left == 0) continue;
cout << "Carry " << carry_left << "\n";
for (auto const& how: carry.second.known) {
cout << " " << how.first << ": ";
how.second.print(cout, carry_left, true);
cout << "\n";
}
}
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "All carry: " << elapsed/1000. << " s\n";
}
mem = get_memory();
if (mem) cerr << "used about " << mem / 1000 / 1000. << " MB\n";
cerr << out.str();
cerr << "Cached " << cached << " results = " << known.size() << " plain + " << cached - known.size() << " carry" << endl;
}
int main(int argc, char const* const* argv) {
try {
my_main(argc, argv);
} catch(exception& e) {
cerr << "Error: " << e.what() << endl;
quick_exit(EXIT_FAILURE);
}
// Cleaning up the datastructures can take ages
quick_exit(EXIT_SUCCESS);
}
Một số trường hợp thử nghiệm:
1: 1: 1
11: 3: 29+
26: 5: 29*8+
27: 3: 39*
100: 5: 19+:*
2431: 9: 56*9*9*1+
3727: 9: 69*7+:*6+
86387: 11: 67*:*1-7*7*
265729: 11: 39*:*:*2/9+
265620: 13: 99*::*6/*7+3*
1921600: 9: 77*:*:*3/
21523360: 9: 99*:*:*2/
57168721: 11: 99*6+:*8-:*
30932: 11: 159*-:4*:*+
56
trực tiếp vào ngăn xếp, làm sao chúng ta có thể đẩy78
vào ngăn xếp?