Задано четыре числа. Требуется разбить их на две пары, чтобы сумма произведений в этих парах была максимальной. Например, для чисел 2,3,4,5 надо вывести 26 (2*3+4*5).
А бывают архисложные, требующие подключения серьёзного математического аппарата:
Для целого положительного числа от 1 до 1000 требуется найти число способов представить его в виде суммы нечётных слагаемых. Порядок слагаемых неважен. Например, для числа 6 надо вывести 4. Сами разбиения выводить не надо:
[1+1+1+1+1+1], [3+1+1+1], [3+3], [5+1]
Я сначала решил "в лоб" - прямым перебором, используя
Set
для фильтрации. Потом ускорил в несколько сотен раз, отказавшись от Set. Это позволило мне обнаружить, что происходит переполнение для больших чисел. Посему мне пришлось заменить long
на BigInteger
, замедлив программу раза в два.fun count(array: IntArray, size: Int): BigInteger { var count = BigInteger.ONE val size2 = size - 2 if (size > 2 && array[size - 1] == 1 && array[size2] == 1) { array[0] += 2 count = count.plus(count(list, array, size2)) array[0] -= 2 var index = 0 while (++index < size2) { if (array[0] == array[index]) continue if (array[0] == array[index] + 2) { array[index] += 2 count = count.plus(count(list, array, size2)) array[index] -= 2 } break } } return count }Тут стало очевидно, что после 170 мы перестаём укладываться в две секунды и подсчёт ответа для 400 занял более двух дней:
in: 100; out: 444793; time: 21 ms in: 200; out: 487067746; time: 7958 ms in: 300; out: 114872472064; time: 1873429 ms in: 400; out: 11962163400706; time: 196301147 ms
Написал код для вывода комбинаций и пытаемся анализировать, чтобы для получившейся выше рекурсивной функции вывести формулу, ибо полный перебор - это уже перебор. 8)
Я не силён в спортивном программировании, но судя по числам (1000) - похоже, стоит воспользоваться динамических программированием со сложностью n*n
ОтветитьУдалитьЭто как?
Удалитьimport java.math.BigInteger
Удалитьfun main() {
val n = readLine()!!.toInt()
val dp = Array(n + 1) { Array(n + 1) { BigInteger.ZERO } }
dp[0][1] = BigInteger.ONE
for (i in 1..n) {
for (max in 1..i step 2) {
for (rem in 1..max step 2) {
dp[i][max] += dp[i - max][rem]
}
}
}
val ans = dp[n].reduce { a, b -> a + b }
println(ans)
}
Идея такая:
Удалитьdp - массив динамического программиования
первый индекс - набираемое число
второй индекс - максимальное используемое число в наборе
Рекурсия: проходим по всем наборам
и в каждом наборе максимальное число будет от 1 до размера набора
rem - остаток (remainder), если уберём максимальное число, то получим ответ для набора, меньшего на это число
Спасибо! Код конечно же работает, но я пока не понимаю почему.
УдалитьМожет голосом? zoom?
УдалитьСоздавайте митинг, я подключусь сразу же.
УдалитьНу, раз не выходит, попробую словами.
УдалитьНадо перебрать все варианты, какими можно представить n нечётными числами.
Попробуем представить это в отсортированном по возрастанию порядке.
Вот этот код, слегка раскрывает идею
fun main() {
val n = readLine()!!.toInt()
val dp = Array(n+1) {Array(n+1) { emptyList>()} }
dp[0][1] = listOf(emptyList())
for (i in 1..n) {
for (max in 1..i step 2) {
for (rem in 1..max step 2) {
dp[i][max] += dp[i - max][rem].map { it + max }
}
}
}
val ans = dp[n].reduce { a, b -> a + b }
println(ans)
}
Введите на входе 6 и получите отсортированный набор вариантов:
Удалить[[1, 1, 1, 1, 1, 1], [1, 1, 1, 3], [3, 3], [1, 5]]
Отсортированный по последнему элементу списка.
УдалитьСпасибо за объяснение, но мне будет проще нарисовать после работы. Код мне понятен, но не совсем понятно, как из имеющегося условия до него дойти самому. Как будто существует такое элегантное решение, а все задачи под него подстраивают.
УдалитьЭто идея динамического программирования (или индукция). Когда мы для решения для n находим решения для значения меньше n.
УдалитьОдна из популярных задач на дп - это рюкзак.
https://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%80%D1%8E%D0%BA%D0%B7%D0%B0%D0%BA%D0%B5
Если будет желание поговорить голосом - я согласен.
Поговорить голосом - это завсегда. Можно завтра по meet, например.
УдалитьПо meet - это как? Что надо установить? Он умеет шарить экран?
УдалитьУ тебя Chrome стоит? Аккаунт, вроде, есть.
Удалитьhttps://meet.google.com/zch-osaz-uot
Кул, похоже, это работает в браузере (хром), и ставить ничего не надо (хром стоит уже).
УдалитьПравда я ни к кому не подключился, так что полной проверки не получилось.
Там только видео связь или можно экран шарить?
Я просто не успел Admit нажать. Ты как-то быстро ушёл
УдалитьШарить можно через Present now
УдалитьОК, наверное, это работает после соедиения.
УдалитьКогда встречаемся в meet?
Если можешь, то сейчас, а то после 14:00 у меня сегодня митинги
Удалить
УдалитьВот статья про динамическое программирование
https://neerc.ifmo.ru/wiki/index.php?title=%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
А вот и Открытая олимпиада по программированию для 3-7 классов
Удалитьhttp://prog.matolimp-spb.org/2021/#dec
3-7 класс? Мишка уже перерос, а Лёшка пока особо не интересуется.
УдалитьПонятно.
УдалитьЕсли Мишке интересно спортивное программирование, то есть раунды и посерьёзнее. Сегодня в 18:05, например, будет двухчасовой раунд. Думаю, в первый раз достаточно будет решить задачку А и для задачек B и C просто почитать условие особо не напрягаясь, если решить не получится. Если будет желание, потом можно будет почитать разбор решений этих задач.
https://codeforces.com/contests/1465
Забыл написать, если будет желание участвовать, то надо зарегистрироваться на раунд заранее. Регистрация даёт право на участие. Но если вы не будете участвовать, то никаких штрафов нет.
УдалитьНапример, рейтинг тоже не будет считаться.
Спасибо, мы зарегистрировались
УдалитьОтлично!
УдалитьНаверное, Мише стоило знать, что в большинстве случаев PyPy будет быстрее, чем Python. Так что если Миша пока хочет остаться на питоне, рекомендую использовать PyPy 3.
Папа, я эту идею не понял.
ОтветитьУдалитьОбъясни пожалуйста.
Какая клёвая задачка, прямо учебный материал!
ОтветитьУдалитьЯ даже потратил на неё сколько-то времени.
Смотри, решение на питоне в лоб не вывозит, хотя пишется очень просто (со списками).
Можно научить запускать профайлер и показать, что реально тормозит. Если заменить списки на просто суммирование, но с рекурсией, то тоже в профайлере видно, что беда.
Если убрать рекурсию, как в примере выше, то питон тоже не вывозит, я проверил, на моём ноуте решение в 2 секунды не укладывается.
Могу ещё проверить с numba, но позже.
Хотя нет, проверил с numba (оказалась под рукой, установлена).
ОтветитьУдалитьСчитает за 75ms, не оптимизировал, матрица хранится флотами.
Какие вы все тут умные! Ставите меня в неловкое положение. Ничего... Ничего... Сейчас прокачаемся 8)
УдалитьФлотами может не зайти.
УдалитьХорошо бы сравнить результат для разных n: 500: 732986521245024
600: 30703043607660730
700: 962056220379782044
800: 23926108358272941056
900: 492244122149966911170
1000: 8635565795744155161506
И, как обычно говорят составители задач на спортивное программирование, среди языков, на которых решение существует, часто есть только C++ и Java
Удалитьhttps://docs.google.com/document/d/e/2PACX-1vRhazTXxSdj7JEIC7dp-nOWcUFiY8bXi9lLju-k6vVMKf4IiBmweJoOAMI-ZEZxatXF08I9wMOQpMqC/pub
If there is a possibility of Java solutions working slow (including, but not limited to C++ solutions working more than a ¼ of TL or C++ solutions using more than 50 MB), a correct java solution is needed.
Решение флотами 100% процентов не зайдёт, там автоматическая валидация, всякие 1e20 точно не устроят :)
УдалитьПросто я хотел проверить, сможет ли питон пробить две секунды лимита на моём ноуте с тройным циклом... Может :)