C ++, explodindo toda a memória em um computador perto de você
Gera a string mais curta em que o cálculo em nenhum lugar causa um estouro de um número inteiro de 32 bits assinado (portanto, todos os resultados intermediários estão no intervalo [-2147483648, 2147483647]
No meu sistema, isso gera uma solução para todos os números até 483432
30 segundos, inclusive, usando a memória 1.8G. Números ainda maiores explodirão rapidamente o uso da memória. O número mais alto que eu posso manipular no meu sistema é 5113906
. O cálculo leva quase 9 minutos e 24 GB. Quando termina internamente, possui uma solução para 398499338
valores, cerca de 9% de todos os números inteiros de 32 bits (positivos e negativos)
Precisa de um compilador C ++ 11. No Linux, compile com:
g++ -Wall -O3 -march=native -std=gnu++11 -s befour.cpp -o befour
Adicione -DINT64
como opção usar um intervalo inteiro de 64 bits em vez de 32 bits para resultados intermediários (isso usará cerca de 50% mais tempo e memória). Isso precisa de um tipo interno de 128 bits. Pode ser necessário alterar o tipo de gcc __int128
. Nenhum resultado em pelo menos o intervalo [1..483432]
muda, permitindo resultados intermediários maiores.
Adicione -DOVERFLOW
como opção não usar um tipo inteiro maior para verificar se há excesso. Isso tem o efeito de permitir estouro e quebra de valor.
Se o seu sistema tiver tcmalloc ( https://github.com/gperftools/gperftools ), você poderá vincular ao resultado, resultando em um programa geralmente mais rápido e que usa um pouco menos de memória. Em alguns sistemas UNIX, você pode usar uma pré-carga, por exemplo
LD_PRELOAD=/usr/lib/libtcmalloc_minimal.so.4 befour 5
Uso básico: gere e imprima todos os números até o destino:
befour target
Opções:
-a
Também imprima todos os números que foram gerados durante o trabalho de destino
-c
Imprima também todos os números que foram gerados começando com um "carry" (dup)
-f
Encontre e imprima o primeiro número além do destino que não foi gerado
-s
Pare se o destino for gerado, mesmo que nem todos os números anteriores tenham sido gerados
-S
Como -s
e -f
em um loop automático. Assim que o alvo for gerado, encontre o primeiro número ainda não gerado e faça com que o novo alvo
-E
Não saia imediatamente quando o objetivo for alcançado. Primeiro termine todas as cordas do comprimento atual
-O
Não produza as strings para todos os números até o destino. apenas a string para o alvo
-o
Instruções permitidas (o padrão é +-*/:
-b num
Literal mais baixo que pode ser enviado (o padrão é 0
)
-B num
Literal mais alto que pode ser enviado (o padrão é 9
)
-r num
O menor resultado intermediário permitido. Usado para evitar o fluxo insuficiente. (o padrão é INT32_MIN
,-2147483648
-R num
O resultado intermediário mais alto permitido. Usado para evitar transbordamentos. (o padrão é INT32_MAX
,2147483647
-m memory
(somente linux) sai quando aproximadamente essa quantidade de memória extra é alocada
Algumas combinações de opções interessantes:
Gere todos os números até o destino e calcule o menor número que precisa de um gerador mais longo do que todos esses números:
befour -fE target
Gere apenas destino (-s), imprima apenas destino (-O)
befour -sO target
Encontre o número mais alto que pode ser gerado no seu sistema, devido a restrições de tempo e / ou memória (isso deixará o sistema sem memória se você deixá-lo em execução. Subtraia 1 da última saída "procurando" que você vê como o último valor seguro ):
befour -S 1
Gere soluções sem nunca usar resultados intermediários negativos ( 30932
é o primeiro valor que precisa de resultados intermediários negativos para a string mais curta):
befour -r0 target
Gere soluções sem pressionar 0
(isso não parece levar a soluções subótimas):
befour -b1 target
Gere soluções, incluindo a..f (10..15)
:
befour -B15 target
Gere soluções sem usar dup :
(adicione -r0
como valores intermediários negativos nunca são interessantes para este caso)
befour -r0 -o "+-*/" target
Encontrar o primeiro valor que não pode ser gerado para um determinado comprimento da corda usando apenas +
, -
, *
e /
:
befour -ES -r0 -o "+-*/" 1
De fato, isso gerará os primeiros termos de https://oeis.org/A181898 , mas começará a divergir em 14771
porque usamos a divisão de truncamento para que o número possa ser feito com uma corda 13 de comprimento em vez de 15 como a série OEIS espera:
14771: 13: 99*9*9*4+9*4/
ao invés de
14771: 15: 19+5*6*7*9+7*8+
Como sem divisão de truncamento parece inútil, a série OEIS pode ser melhor gerada usando
befour -ES -r0 -o"+-*" 1
Supondo que a divisão permaneça inútil, isso me deu três termos extras antes de eu ficar sem memória:
10, 19, 92, 417, 851, 4237, 14771, 73237, 298609, 1346341, 6176426, 25622578
Outra versão deste programa que armazena parte dos dados em arquivos externos adiciona 135153107 e 675854293, após o qual todos os números inteiros de 32 bits foram gerados.
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);
}
Alguns casos de teste:
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
diretamente para a pilha, como podemos empurrar78
para dentro da pilha?