На форумі обговорюються лише питання, пов'язані з олімпіадою
Ви не зайшли.
Продовжую відновлювати традицію
Викладаю свої розв'язки з коментарями і закликаю інших ділитися своїми. Особливо якщо задачі розв'язані якимись іншими ідеями.
Chess22k17
На перший погляд може здатися, що задача розв'язується аналогічно до задачі Chess2k17 з першого туру. Але насправді все трохи складніше. Для неквадратних дошок просте розділення дошки навпіл по горизонталі і вертикалі не завжди дасть оптимальне розміщення тур. Простий приклад, щоб переконатися: дошка 10х15. Оптимальне розміщення тур виглядає ось так:
Розміщено 36 тур кожного кольору. Тоді як розділення чорних та білих фігур по "центральному хресту" дало б лише 35 фігур.
Тож для знаходження правильної відповіді необхідно знайти такі числа a і b, щоб значення min(a * b, (n - a) * (m - b)) було максимальним. Перебір усіх значень a і b для обмежень задачі не зайде по часу, але можна перебирати значення тільки вздовж однієї вісі. Очевидно, що значення min(a * b, (n - a) * (m - b)) буде максимальним тоді, коли значення a * b та (n - a) * (m - b) - рівні (адже збільшення одного з них неминуче призведе до зменшення іншого, а функція min безжалісно поверне менший результат). Тож перебирати будемо лише значення a. А значення b знайдемо з рівняння a * b = (n - a) * (m - b). Але ми працюємо з цілими числами, тому для кожного значення a будемо перевіряти по 2 значення b - округлене догори та донизу.
Ось і все. Плюс кілька невеликих оптимізацій:
- якщо обидві розмірності дошки - парні, то можна повертати значення n * m / 4, як і в задачі першого туру
- перебираємо вздовж меншої з розмірностей. На асимптотику не вплине, але на частині тестів може дати помітний виграш у швидкості.
- перебирати a можна тільки до середини дошки. Теж на асимптотику не вплине, але гарантовано зробить обчислення вдвічі швидшими.
Асимптотика рішення - O(min(N, M))
Код рішення на С++.
#include <iostream> using namespace std; long long solve(long long n, long long m) { if (n % 2 == 0 && m % 2 == 0) { return n * m / 4; } long long res = -1; for (long long a = 0; a <= n / 2; ++a) { long long b = m - a * m / n; long long r1 = min(a * b, (n - a) * (m - b)); b -= 1; long long r2 = min(a * b, (n - a) * (m - b)); long long r = max(r1, r2); if (r > res) { res = r; } } return res; } int main() { long long n, m; cin >> n >> m; if (n > m) { swap(n, m); } cout << solve(n, m) << endl; }
Br2k17
Задача можна зробити кількома способами. Почну з найбільш чесного)
Якщо забути про правило, що забороняє вставляти квадратні дужки всередину круглих, то задача буде "класичною" - для неї існують готові формули і для обчислення напряму і для обчислення за допомогою ДП. Познайомитися з цими формулами і їх виведенням можна, наприклад, на e-maxx.ru в розділі Количество последовательностей. Але згадане обмеження трохи псує життя. За основу візьмемо ДП рішення і спробуємо в ньому це обмеження врахувати.
Перш за все помітимо, що для знаходження кількості послідовностей довжини n нам уже недостатньо знати, скільки є послідовностей довжин 0...(n-1). Нам ще треба знати, які з них містять квадратні дужки, а які ні. Тому заведемо 2 масива: dp[k] - кількість "якісних" дужкових послідовностей довжини k, та dps[k] - кількість "якісних" дужкових послідовностей довжини k, які містять квадратні дужки.
Очевидно, dp[0] = 1, бо пуста дужкова послідовність є якісною, а dps[0] = 0, бо квадратних дужок пуста послідовність не містить.
Тепер треба придумати формули для загального випадку. Почнемо з dp (тут варто заглянути в статтю на e-maxx, якщо ще цього не зробили, бо деякі речі, які там пояснюються, тут я пояснювати не буду).
Квадратні та кутові дужки ми можемо поставити у будь-якому випадку. Тому можемо додати dp[j] * dp[n - j - 1] * 2 до dp[n]. Круглі дужки ми можемо поставити тільки навколо послідовностей, які не містять квадратних дужок. При цьому праворуч від поставлених дужок може бути будь-яка "якісна" послідовність. Тож додаємо ще (dp[j] - dps[j]) * dp[n - j - 1].
Тепер масив dps. З ним все трохи складніше. Треба врахувати наступні випадки:
- квадратні дужки ставляться навколо послідовності без квадратних дужок і праворуч від вставлених дужок теж немає квадратних. Значення (dp[j] - dps[j]) * (dp[n - j - 1] - dps[n - j - 1])
- навколо послідовності, що містить квадратні дужки ставимо ще квадратні або кутові дужки (круглі не можемо за умовою). Праворуч від вставлених дужок будь-яка "якісна" послідовність (і з квадратними дужками і без). Значення dps[j] * dp[n - j - 1] * 2
- навколо послідовності, що не містить квадратних дужок ставимо дужки будь-якого з трьох видів. При цьому послідовність праворуч від вставлених дужок містить квадратні дужки. Значення (dp[j] - dps[j]) * dps[n - i - 1] * 3
Ось і все. Тут багато всяких дрібниць, на яких можна помилитися, тому для перевірки коректності отриманих формул може бути зручно просто "в лоб" написати якийсь генератор якісних дужкових послідовностей певної довжини і звіряти відповідь ДП з кількістю послідовностей, виданих генератором.
Також можна помітити, що значення відповіді ростуть дуже швидко і для N = 100 відповідь у жоден стандартний тип не влізе. Тож доведеться скористатися довгою арифметикою. На ній окремо зупинятися не буду, є купа туторіалів і просто готових реалізацій для більшості мов, а деякі мови взагалі мають вбудовану довгу арифметику в самій мові (пайтон), або в стандартній бібліотеці (джава). У маштабах довгої арифметики значення тут не дуже великі, тому сішникам/паскалістам можна було брати наївні реалізації того-ж множення і не мучитися з Карацубою, чи FFT.
Щодо не зовсім чесних рішень. Діапазон вхідних даних дуже невеликий, тому можна було нагенерувати масив констант і заслати його, щоб не переживати з приводу тайм лімітів.
Ну і зовсім для чітерів. В OEIS є послідовність A165976, яка і є відповіддю на задачу. Тож масив констант можна було навіть не генерувати самому, а взяти готовий.
Асимптотика "чесного" рішення - O(N^2). Чітерського - очевидно, O(1)
Код "чесного" рішення на С++.
Структура bigint для роботи з довгою арифметикою лежить окремо ось тут, щоб не сильно відволікати від основної ідеї розв'язку.
#include <iostream> #include <algorithm> #include <iomanip> using namespace std; int main() { int N; cin >> N; bigint dp[101]; bigint dps[101]; dp[0] = 1; dps[0] = 0; for (int n = 1; n <= N; ++n) { dp[n] = dps[n] = 0; for (int i = 0; i < n; ++i) { dp[n] += dp[i] * dp[n - i - 1] * 2 + (dp[i] - dps[i]) * dp[n - i - 1]; dps[n] += (dp[i] - dps[i]) * (dp[n - i - 1] - dps[n - i - 1]) + dps[i] * dp[n - i - 1] * 2 + (dp[i] - dps[i]) * dps[n - i - 1] * 3; } } cout << dp[N] << endl; }
Pal2017
Перевертаючи підрядки довільної довжини ми могли б отримати будь-яку перестановку символів початкового рядка. Але якщо рядок, що ми розвертаємо повинен мати непарну довжину, то ми не зможемо змінити парність позицій символів: символи, що стояли на парних позиціях - залишаться на парних, а ті, що стояли на непарних - залишаться на непарних. Але крім цього ніяких обмежень не з'являється, тобто в рамках парних і непарних позицій ми все ще можемо отримати будь-яку перестановку шляхом розворотів.
Таким чином, нам не дуже важливий сам початковий рядок. Важливо лише які символи знаходяться на парних позиціях, а які на непарних.
Щодо самого рішення. Окремо варто обробити два випадки: коли довжина початкового рядка парна і коли непарна. Якщо довжина парна, то відповідні символи з початку і з кінця паліндрому матимуть різну парність позицій. А якщо непарна, то однакову.
Для рядків непарної довжини ми можемо окремо зібрати 2 паліндроми для символів на парних і непарних позиціях, а потім з цих двох рядків отримати кінцевий.
А для рядків парної довжини ми можемо просто перебрати всі символи у порядку зростання і кидати половину з них на початок рядка-результату, а половину - на кінець. Перевіряючи, щоб кожного символу була однакова кількість на парних і непарних позиціях.
В кінці перевіряємо отриманий рядок на паліндромність і виводимо або його, або "-1".
Асимптотика мого рішення - O(N * LogN). При бажанні можна було б замінити словники на масиви і отримати лінійну асимптотику, але суттєвого виграшу по швидкості це не дасть.
Код рішення на С++.
#include <iostream> #include <map> #include <string> using namespace std; string stringFromEvenOdd(const string &even, const string &odd) { string result(even.length() + odd.length(), '_'); for (int i = 0; i < even.length(); ++i) { result[2*i] = even[i]; } for (int i = 0; i < odd.length(); ++i) { result[2*i + 1] = odd[i]; } return result; } string getPaliOdd(map<char, int> &counts) { string result; string end; string central; for (map<char, int>::iterator it = counts.begin(); it != counts.end(); ++it) { if (it->second & 1) { if (central.empty()) { central += it->first; } else { return ""; } } result.append(it->second / 2, it->first); end.append(it->second / 2, it->first); } result += central; result.append(end.rbegin(), end.rend()); return result; } string getPaliEven(map<char, int> &countsEven, map<char, int> &countsOdd, map<char, int> &countsTotal) { string result; string end; for (map<char, int>::iterator it = countsTotal.begin(); it != countsTotal.end(); ++it) { char c = it->first; int cnt = it->second; if (countsEven[c] != countsOdd[c]) { return ""; } result.append(cnt/2, c); end.append(cnt/2, c); } result.append(end.rbegin(), end.rend()); return result; } string solve(map<char, int> &countsEven, map<char, int> &countsOdd, map<char, int> &countsTotal, int n) { string res; if (n & 1) { string even = getPaliOdd(countsEven); string odd = getPaliOdd(countsOdd); res = stringFromEvenOdd(even, odd); } else { res = getPaliEven(countsEven, countsOdd, countsTotal); } string::iterator i = res.begin(); string::reverse_iterator ir = res.rbegin(); bool isOk = true; for (; isOk && i != res.end() && ir != res.rend(); ++i, ++ir) { isOk = *i == *ir; } if (isOk && res.length() == n) { return res; } else { return "-1"; } } int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); map<char, int> countsEven; map<char, int> countsOdd; map<char, int> countsTotal; string s; cin >> s; for (int i = 0; i < s.length(); ++i) { char c = s[i]; map<char, int> &target = i & 1 ? countsOdd : countsEven; target[c]++; countsTotal[c]++; } cout << solve(countsEven, countsOdd, countsTotal, s.length()) << endl; }
Robotsway
Задача по суті на єдину комбінаторну формулу перестановок з повтореннями.
Перебираємо кількість "довгих" відрізків шляху по k м. Позначимо цю кількість за l. І для кожного l додаємо до результату значення кількості перестановок з повтореннями: m!/(l!*o!), де l - кількість довгих сегментів шляху, o - кількість коротких, а m - загальна кількість відрізків у шляху. Очевидно, m = o + l. l ми перебираємо, o = n - k * l, m = n - k * l + l = n - l * (k - 1).
Не дивлячись на купу факторіалів, кінцевий результат для обмежень з умови завжди поміщається в 64-бітний цілий тип. Але проміжні обчислення можуть не поміщатися, якщо рахувати значення перестановок в лоб.
Тому треба рахувати більш акуратно.
Перш за все можна скоротити формулу ось так:
m!/(l!*o!) = (1*2*...*(m-1)*m)/(l!*o!) = ((l+1)*(l+2)*...*(m-1)*m)/o!
Тобто можна по-перше повністю позбутися одного з факторіалів, а по-друге - зменшити кількість обчислень для одного з факторіалів, що залишилися. Ну і очевидно, скорочувати варто на більший з факторіалів, не обов'язково саме на l!
Але цього все одно може бути недостатньо, тому замість обчислення чисельника і знаменника окремо варто завести дріб, у якому поступово домножувати чисельник і знаменник і після кожного множення цей дріб скорочувати.
Асимптотика - O(N^2 * LogN / K).
Код рішення на С++.
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; struct Fraction { long long numerator; long long denominator; Fraction(long long num, long long denom = 1): numerator(num), denominator(denom) {} Fraction &operator *=(long long n) { numerator *= n; relax(); return *this; } Fraction &operator /=(long long n) { denominator *= n; relax(); return *this; } long long getValue() const { return numerator / denominator; } private: void relax() { long long d = gcd(numerator, denominator); numerator /= d; denominator /= d; } static long long gcd(long long a, long long b) { return b == 0 ? a : gcd(b, a%b); } }; long long permutations(long long total, long long group1, long long group2) { long long num = max(group1, group2) + 1; long long denomEnd = min(group1, group2); Fraction f(1); long long denom = 2; while (num <= total && denom <= denomEnd) { if (f.numerator < f.denominator) { f *= num++; } else { f /= denom++; } } while (num <= total) { f *= num++; } while (denom <= denomEnd) { f /= denom++; } return f.getValue(); } long long solve(long long n, long long k) { int maxLongSegments = (n - 1) / k; long long res = 0; for (int lSegs = 1; lSegs <= maxLongSegments; ++lSegs) { res += permutations(n - lSegs * (k - 1), lSegs, n - lSegs * k); } return res; } int main() { long long n, k; cin >> n >> k; cout << solve(n, k) << endl; }
Transmission
Знайти відстань між центрами, знаючи радіуси і довжину ременя важко. Зате знайти довжину ременя, знаючи відстань між центрами дисків та радіуси можна дуже просто. До того ж, зі збільшенням відстані збільшуватиметься і довжина ременя. А отже можна застосувати бінарний пошук.
Але спершу виведемо формулу довжини ременя для заданих радіусів дисків та відстані між центрами. Схоже, що з цим у багатьох учасників були проблеми, бо вони брали готові формули з інженерних статей/довідників, не дуже замислюючись над тим, звідки ті формули взялися .
В інженерних обчисленнях формули часто спрощують. Особливо "страждає" тригонометрія. Наприклад, для невеликих кутів sin x можуть замінити на x, і т.п. І це абсолютно нормально для інженерних цілей. По-перше, в житті кути дійсно будуть невеликі, бо пасові передачі майже завжди мають співвідношення швидкостей не більше 1:3 і диски розташовані достатньо далеко з суто практичних міркувань - так менша сила тертя і менше зношуються ремені. А по-друге - абсолютно точні обчислення нікому і не треба: ніхто не виготовлятиме ремінь з нанометровою точністю, а якби і виготовляли, то через ту саму силу тертя ремінь буде нагріватися і розширюватися, змінюючи свою ефективну довжину на значення значно більші, ніж точність обчислення за "правильними" формулами.
До формули.
Пряма DE проведена паралельно прямій O1O2.
Радіуси O1F і O2E також паралельні, бо вони перпендикулярні до дотичної FE.
Тоді O1DEO2 - паралелограм. А отже DE = O1O2 = d
Кути FDE і FO1O2 рівні як відповідні. Також вони рівні θ/2.
Із прямокутного трикутника FDE знайдемо косинус кута FDE. Він дорівнює FD / DE = (r1-r2) / d
Тоді кут FDE = acos((r1-r2) / d).
Отже θ = 2 * acos((r1-r2) / d)
Знаючи кут θ ми можемо знайти довжини частин ременя, що обтікають диски - це r1 * (2π - θ) для більшого диску і r2 * θ для меншого.
Залишилось знайти довжину відрізка дотичної. Повернемось у трикутник FDE і знову згадаємо тригонометрію.
FE = DE * sin(FDE) = d * sin(θ/2)
Таким чином, загальна формула довжини ременя має вигляд:
2 * d * sin(θ / 2) + r1 * (2π - θ) + r2 * θ
Любителі готових формул могли її взяти на сторінці англомовної вікіпедії: https://en.wikipedia.org/wiki/Belt_problem (так, картинку я свиснув саме звідти).
Тепер, як я й казав, просто застосовуємо бінарний пошук. Автор з якоїсь незрозумілої мені причини мовчав про необхідну точність результату, тому використовуємо найбільш точний з доступних тип чисел з плаваючою крапкою (long double в C++), а бінарному пошуку даємо можливість з запасом виконуватися протягом 100 ітерацій. Цього буде більш ніж достатньо, щоб отримати 20 знаків відповіді (хоча чекер, схоже, перевіряє з точністю 1e-3).
Асимптотика рішення - O(1)
Код рішення на С++.
#include <iostream> #include <cmath> #include <iomanip> using namespace std; typedef long double ld; const ld PI = acosl(-1); ld getL(ld r1, ld r2, ld d) { ld theta = 2 * acosl((r1 - r2)/d); return 2 * d * sinl(theta / 2) + r1 * (2 * PI - theta) + r2 * theta; } ld solve(ld L, ld R1, ld R2) { ld r = L /2; ld l = R1 + R2; ld m; for (int i = 0; i < 100; ++i) { m = (r + l) / 2; if (m == r || m == l) { break; } if (getL(R1, R2, m) < L) { l = m; } else { r = m; } } return m; } int main() { ld r1, r2, l; cin >> r1 >> r2 >>l; if (r1 < r2) { swap(r1, r2); } ld res = solve(l, r1, r2); cout << scientific << setprecision(20) << res << endl; }
Відредаговано Dim_ov (2017-12-24 05:29:22)
Поза форумом
Четвертая задача намного легче. Задачу можно переформулировать так: "Сколькими способами можно добраться до последней ступеньки, прыгаю на следующюю или через (k-1), но нельзя прыгать прыжком одного вида."
var a:array[-10..100] of qword; n,k,i:longint; begin readln(n,k); a[1]:=1; for i:=1 to k-1 do a[i]:=1; a[k]:=2; for i:=k+1 to n do a[i]:=a[i-1]+a[i-k]; dec(a[n]); if (n mod k=0) then dec(a[n]); writeln(a[n]); end.
Поза форумом
Все решения на python, набирают максимальный результат.
Сhess22k17
M, N = map(int,raw_input().split()) if M%2==0 and N%2==0: print M*N>>2 else: a, b = min(M,N), max(M,N) limit = int(((a+b-1)/4.0*a/b)**0.5) z = [max((a-i)*(b*i/a), i*(b*(a-i)/a)) for i in xrange(a/2-limit,a/2+1)] print max(z)
Решение удалось провести за O(sqrt(min(M,N)) доказав, что точный максимум на прямой, смещенной относительно центра больше, чем на limit (т.е. максимум min(a * b, (n - a) * (m - b)) при фиксированном a=n/2-limit и непрерывном b), меньше, чем число тур, соответсвующее имеющимся точкам около центра. При M=N=1e6 limit=sqrt(500000)~700.
Br2k17
N = input() C_2 = [1]#C_2[i] = Catalan(i)*2**i C_3 = [1]#3 types of braces for i in xrange(1,N+1): nxt2, nxt3 = 0, 0 for j in xrange(i): nxt2 += C_2[j]*C_2[i-j-1]*2 nxt3 += (2*C_3[i-j-1]+C_2[i-j-1])*C_3[j] C_2.append(nxt2) C_3.append(nxt3) print C_3[-1]
Решение вытекает из википедийного доказательства рекуррентного соотношения для чисел Каталана, обобщением на три типа скобок.
Его можно ускорить почти в 2 раза, если не считать nxt2, а использовать более простое рекуррентное соотношение
C_2.append(C2[-1]*4*(2*i-1)/(i+1)).
Итого, самое труЪ решение будет таким:
N = input() C_2 = [1]+N*[0]#C_2[i] = Catalan(i)*2**i C_3 = [1]+N*[0]#3 types of braces for i in xrange(N): for j in xrange(i): C3[i+1] += (2*C_3[j]+C_2[j])*C_3[i-j] C_2[i+1] = C_2[i]*4*(2*i+1)/(i+2) print C_3[-1]
Отправляю порцию печенек на темную сторону силы для FordPerfect.
Pal2017
Решение некрасивое. Питон дает преимущество только при четной длине входных данных
S = raw_input() digits=[list(S[0::2]), list(S[1::2])] def merge(even, odd):#valid only for odd full length!!! ans = "" for i in xrange(len(odd)): ans += even[i]+odd[i] ans += even[-1] return ans def counts(digs):#count number of digits' occurrences ret = [0]*10 for s in digs: ret[int(s)] += 1 return ret def not_ok(digs):#returns True if input is not palindromizable return sum(map(lambda x:x%2, counts(digs))) != len(digs)%2 def palize(digs):#palindromize a string cnts = counts(digs) half, center = "", "" for i in xrange(10): half += cnts[i]/2*str(i) center += cnts[i]%2*str(i) return half + center + half[::-1] if len(S)%2: if not_ok(digits[0]) or not_ok(digits[1]): print "-1" else: print merge(palize(digits[0]), palize(digits[1])) else: digits[0].sort() digits[1].sort() if digits[0] != digits[1]: print "-1" else: print ''.join(digits[0]+digits[0][::-1])
Robotsway
n,k = map(int,raw_input().split()) R = [1]*k while len(R)<=n: R.append(R[-1]+R[-k]) print R[-1] - 1 - int(not n%k)
Transmission
Нелинейное уравнение решай методом Ньютона! Для этого уравнения этот метод особенно хорош, так как производная невязки является няшной алгебраической функцией
from math import pi, asin, sqrt R1, R2, L = map(float, raw_input().split()) def residual(dist): phi = asin((R1-R2)/dist) return pi*(R1+R2)+2.0*sqrt(dist*dist-(R1-R2)*(R1-R2))+2.0*(R1-R2)*phi-L tmp1 = (L-pi*(R1+R2))/2.0 init = sqrt(tmp1*tmp1-(R1-R2)*(R1-R2)) precision = L*1e-14 x = residual(init) while abs(x) > precision: tmp = (R1-R2)/init der = 2*sqrt(1-tmp*tmp) init -= x/der x = residual(init) print repr(init)
Ответ с относительной погрешностью 1e-14 находит за 3-4 итерации, а может даже и максимум за 3 для моего initial guess, уже не помню. Дальше не мудрил.
PS Да, этот код работает даже при while x>precision: вместо while abs(x) > precision: , но никому так делать не советую - чревато.
Відредаговано andervish (2017-12-25 19:28:34)
Поза форумом
не понятно
andervish написав:
т.е. максимум min(a * b, (n - a) * (m - b)
, а в коде у вас два раза максимум. Так же вы используете извлечение корня который не будет работать с длинной арифметикой.
Ну и по-поводу асимптотической сложности, если я не ошибаюсь функция в задаче допускает тернарный поиск, соответственно можно добиться O(log(min(N,M))), что то по типу:
M, N = map(int,raw_input().split()) def solve(M,N): if M%2==0 and N%2==0: return M*N>>2 else: a, b = min(M,N), max(M,N) def f (i): return min((a-i)*(b*i/a), i*(b*(a-i)/a)) l,r = 0, a/2 while (r-l)>=3: m1 = l+(r-l)/3 m2 = r-(r-l)/3 if f(m1)<f(m2): l=m1 else: r=m2 return max(f(l),f(l+1),f(r)) print solve(M,N)
Відредаговано ikovrigin (2017-12-25 23:43:05)
Поза форумом
ikovrigin написав:
два раза максимум
Очень хороший вопрос. После переименования сторон в a и b я рассматриваю задачу в постановке max(min(i*(b-j),(a-i)*j)). При фиксированном i максимум находится в одной из целых точек, приближающих дробь b*i/a сверху или снизу. Если взять точку сверху, то j>=b*i/a, следовательно min[i*(b-j), (a-i)*j]= i*(b-j), если снизу, то j<=b*i/a, и min[i*(b-j), (a-i)*j]a-i)*j. Для точки сверху b-j = (a-i)*b//a, для точки снизу j = b*i//a. Итого искомый максимум при фиксированном i равен max(i*(b*(a-i)//a)), (a-i)*(b*i//a)), что и считается в коде.
ikovrigin написав:
извлечение корня который не будет работать с длинной арифметикой
Тут не совсем понял, что имется в виду. При ограничениях, наложенных на входные данные limit не больше 1000. Да и корень с длинными целыми работает адекватно.
>>> sqrt(1000000000000000000000000000000000000000000000000000000000) 3.1622776601683795e+28 >>> 1000000000000000000000000000000000000000000000000000000000**0.5 3.1622776601683795e+28 >>> int(1000000000000000000000000000000000000000000000000000000000**0.5) 31622776601683794750641012736L
Идея с тернарным поиском очень красивая. Нужно только показать, что z(i) = max(i*(b*(a-i)//a)), (a-i)*(b*i//a)) является унимодальной, что неочевидно.
UPD z(i) не унимодальна. http://rextester.com/ECELB82622 (c) известно кто.
PS Поскольку выражения в max в z(i) переходят друг в друга при замене i->a-i, то можно чуть-чуть упростить код за счет симметризации интервала.
M, N = map(int,raw_input().split()) if M%2==0 and N%2==0: print M*N>>2 else: a, b = min(M,N), max(M,N) limit = int(((a+b-1)/4.0*a/b)**0.5) z = [(a-i)*(b*i/a) for i in xrange(a/2-limit,a/2+2+limit)] print max(z)
Новое z(i) тоже не унимодально.
Відредаговано andervish (2017-12-29 23:37:34)
Поза форумом
Спасибо за развернутый ответ.
Насчет корня мое замечание было не верным, потому что вы используете корень как приближение.
А вот насчет того что корень работает корректно с длинными целыми вызывает у меня сомнения, вроде он извлекает используя float, поэтому
from math import sqrt
i = 111111111111111111
i = int(sqrt(i*i))
print i
Результат не радует 111111111111111120
Поза форумом