#include <iostream>
#include <vector>
#include <cmath>
#include <cassert>
using namespace std;
vector<unsigned long long> a(101,0);
// O(N) . Простой перебор. Для данного N перебираем
// все возможные разбиения числа на два слагаемых.
// Получаем две задачи меньшего размера. Решив их
// , собираем решение исходной выбрав то
// которое дает наибольшее произведение
unsigned long long task128_helper(unsigned int n) {
if (a[n]) {
return a[n];
}
for (unsigned int i = 1; i <= n/2; ++i) {
a[n] = max(a[n], task128_helper(i)*task128_helper(n-i));
}
return a[n];
}
unsigned long long task128(unsigned int n){
if (n) {
return a[n] ? a[n] : task128_helper(n);
}
return 0;
}
// Посмотрев на то что получилось на выходе предыдущего алгоритма
// можно заметить что решения имеют вид 3^n*2^k. Это должно иметь под
// собой какое-то обоснование. Для начала поймем почему другие слагаемые не нужны.
// Например возмем 5. Если 5 разложить на сумму 3 и 2 , то видно что в таком случае
// произведение равно шести и вообще n <= 2^(n/2) и n <= 3^(n/3). Далее, функция
// 2*2*2...3*3 возрастает по мере увеличения числа троек. Из этого можно сделать
// вывод что нам выгоднее иметь как можно больше троек.
// Итак осталось только решить , что делать когда число не делится на 3
// Остаток 2 - Тут все просто просто берем эту двойку
// Остаток 1 - Уберем одну тройку и возьмем две двойки потому что 2*2>3*1
unsigned long long task128_fast(unsigned int n) {
unsigned long long mult = 1;
switch (n % 3) {
case 1:
mult = 4;
n -= mult;
break;
case 2:
mult = 2;
n -= mult;
break;
default:
mult = 1;
break;
}
unsigned int k = n / 3;
return mult * (unsigned long long)pow(3, k);
}
int main() {
a[0] = 0;
a[1] = 1;
a[2] = 2;
a[3] = 3;
for(int i = 4; i < 101; ++i) {
auto a = task128(i);
auto b = task128_fast(i);
cout << i << " -> " << a << " -> " << b << endl;
assert(a == b);
}
return 0;
}