Ответы пользователя по тегу Алгоритмы
  • Как найти максимум в бинарном дереве Java?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    То, что вы внутри функции присваиваете int параметру, снаружи функции не видно (переменная передается по значению, а не по ссылке). Поэтому результат рекурсивных вызовов надо брать из возвращаемого значения, а не надеятся, что функция сама обновит tmpMaxValue.
    Ответ написан
    Комментировать
  • Как решить задачу с CodeWars Simple Fun #159: Middle Permutation?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Немного пальцем в небо, но попробуйте заменить "/" на "//". Вам же надо в целых числах считать. Возможно, питон пытается блинные числа приводить к даблу и потом назад к длинными и теряет точность.

    Вместо ceil(a/b) используйте (a+b-1)//b
    Вместо floor(a/b) или, когда делится без остатка используйте a//b

    В логике решения ошибки я не вижу.
    Ответ написан
    4 комментария
  • Как организовать такое поведение?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    У вас весьма услржненный вариант Set cover. Пишите полный перебор.

    Могу лишь дать пару советов по реализации.
    Пишите рекурсивную функцию которой передается оставшиеся не распределенные блюда и список уже готовых наборов (наборы из одного блюда - тоже наборы). Пытайтесь куда-то приспособить первое нераспределенное блюдо.

    Если наборы не большие (скажем, не более четырех типов блюд) и всего блюд в заказе не много, то можно делать в тупую - перебирайте в функции все 2-3 блюда, кроме первого, двумя-тремя вложенными циклами. Потом берете типы этих блюд и смотрите, а есть ли у вас такой набор. Наборы можно хранить как список списков и искать там бинарным поиском или линейно. Или можно завести 4-х мерный массив и дополнить короткие наборы заглушкой вида "" до 4-х блюд. Такой ассоциативный набор для каждой комбинации типов блюд будет хранить есть ли такой набор и сколько он стоит.

    Если же наборы могут быть больше или всего блюд многовато и надо бы побыстрее решать, то делайте следующее:
    Для каждого типа блюд заведите список наборов, в которые такой тип входит.
    В функцию передавайте блюда, сгруппированные по типам, и уже собранные наборы. Потом циклом перебирайте какой тип набора будете собирать с первым блюдом (у вас же есть список всех подходящих наборов) и вызывайте вторую рукурсивную функцию, которой передаются несобранные блюда, уже готовые наборы, какой тип набора собирается, и собранная часть текущего набора. Функция2 будет брать следующий неиспользованный тип в текущем наборе и, если блюда такого типа у вас есть, добавлять в цикле одно такое блюдо к набору и вызываться рекурсивно. Если весь набор собран, то рекурсивно вызывайте первую функцию.
    В первой функции, помимо перебора всех наборов, после цикла, просто берите первое блюдо как само себя.

    Когда первая функция получает пустой набор нераспределенных блюд, готовые наборы сравнивайте на оптимальность с глобальным ответом.
    Ответ написан
    Комментировать
  • Как решать задачи на ДП такого типа (выбрать предметы, но без повторений)?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Это классическая задача о рюкзаке.

    Можно составить такое ДП: F(c, k) - максимальное количество страниц, которые можно набрать из первых k книг общей стоимостью ровно c.

    База: F(0,0) = 0, F(0,*) = F(*,0) = -infinity.
    Пересчет:
    F(c, k) = max(F(c-cost[k], k-1) + pages[k], F(c,k-1))


    Это ДП приведено в википедии, например. Ответ - максимум по всем c F(c,k).

    Есть одна хитрость при реализации - можно обойтись только одной строкой этой матрицы, если вам не нужно восстанавливать весь набор книг в ответе, а только его лучшее значение (только надо поменять параметры местами - строка размером с максимальную цену). Это сократит потребление памяти в K раз, если у вас K книг. На скорость работы решения это не влияет.

    Сначала реализуйте все дп на матрице снизу вверх, двумя циклами.
    Теперь, если вы будете гнать внутренний цикл с конца к началу, то вам не понадобится смотреть на уже переписанные на текущей итерации внешнего цикла значения, и можно забить на второй параметр ДП и просто переписывать строку на месте.
    Ответ написан
    Комментировать
  • Какой алгоритм можно применить для задачи размещения числа в фиксированные контейнеры?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Ваша задача очень похожа на задачу о рюкзаке.

    Во первых, можно забыть про заглушки, если требовать собрать ровно от N до 1.25N секций.
    Т.е. в вашем примере вам можно собрать от 300 до 400 секций.

    Теперь ваша задача в точности становится задачей о рюкзаке, количество секций в контейнере - это "длина" вещи из задачи о рюкзаке, а длина - это "цена".

    И вам надо, как в задаче о рюкзаке, набрать каких-то "вещей", т.ч. рюкзак заполнен от 300 до 400 и суммарная "цена" минимальна.

    Если контейнеров не много (до 20-25), то можно решать полным перебором: рекурсивной функцией перебираете сколько контейнеров каждого типа вы берете в ответ. Если суммарное количество секций выше 1.25N, то останавливаетесь. В конце, когда перебрали все контейнеры, если набрали от N до 1.25N секций, то сравниваете текущую длину с оптимальным ответом и запоминаете, если длина меньше.

    Довольно быстрое решение - через динамическое программирование. Заведите массив от 0 до 1.25N. В нем будет хранится минимально возможная длина для набора контейнеров с заданным количеством секций. Изначально заполните его -1, а в индекс [0] положите 0.0.

    Теперь для каждого контейнера пройдитесь по массиву слева направо и, если текущая длина неотрицательна, то попробуйте приложить текущую секцию к набору, добавив количество секций к индексу, а длину к значению массива. Если по новому индексу лежит что-то хуже, чем то что вы насчитали, перезапишите это значение. Я php не знаю, вот вам решение на C++.

    // Container - структура содержащая длину length и количество секций sections.
    int maxn = n*5/4; // Округленное вниз целое число.
    vector<float> length(maxn+1, -1.0);
    length[0] = 0.0;
    vector<container> best(maxn+1);
    for (Container c: containers) {
      for(int i = 0; i < maxn; ++i) {
        if (length[i] < 0) continue;
        int j = i + с.sections;
        if (j <= maxn && (length[j] < 0 || length[j] > length[i] + c.length)) {
          length[j] = length[i] + c.length;
          best[j] = c;
        }
      }
    }
    int besti = -1;
    float bestlen = 100000000;
    for (int i = n; i <= maxn; ++i) {
      if (length[i] > 0 &&  length[i] < bestlen) {
        besti = i;
        bestlen = length[i];
      }
    }
    if (besti == -1) {
      cout << "Нет решения" << endl;
    } else {
      cout << "Минимально возможная длина: " << length[besti] << endl;
      cout << "Контейнеры:" << endl;
      while (besti > 0) {
        cout << best[besti].length << endl;
        besti -= best[besti].sections;
      }
    }


    Лучше, все-таки, если вы сможете задать длины целыми числами, например, в миллиметрах. Тогда не будет проблем с точностью. Это слегка адаптированный под вашу задачу стандартный алгоритм динамического программирования для задачи о рюкзаке. Погуглите, почитайте википедию. Там еще указаны различные аппроксимационные алгоритмы. Если у вас ОЧЕНЬ много секций (N>10^8) и данный алгоритм требует слишком много памяти, но вам пойдет и достаточно хорошее решение, пусть и не оптимальное, то используйте аппроксимационные алгоритмы.

    Если хотите изменить допустимые проценты заглушек - меняйте коэффициент 5/4 в коде.
    Важно: это решение предполагает, что все контейнеры допускают одинаковый процент заглушек.
    Решение уже ищет минимальное количество заглушек при минимально возможной общей длине.

    Edit: исправил код восстановления ответа - сначала забыл разобрать случай недостижимых состояний (-1 в массиве length)
    Ответ написан
    2 комментария
  • Какие есть достойные альтернативы жадному алгоритму (greedy)?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Ваша задача - Мультипликативный 0-1 рюкзак. Тут надо упомянуть NP и другие страшные слова, которые означают, что хорошего решения тут нет. Жадность - если подойдет какая-то аппроксимация оптимального ответа. Стоит пытаться собирать числа с самых мелких, пытаясь засунуть туда как можно большие числа.
    Метод ветвей и границ (фактически полный перебор с оптимизациями и эвристиками).

    Можно записать задачу в виде целочисленного линейного программирования и натравить на нее любой из существующих решателей. Есть хорошие шансы, что это очень быстро найдет оптимальное решение.
    Ответ написан
    Комментировать
  • Есть ли простой алгоритм для парсинга блоков кода?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вам надо сопоставить каждой открывающей скобке - соответствующую закрывающую.
    В простейшем случае это делается одним проходом по тексту со стеком.

    Встретили открывающую скобку - положили ее позицию в стек. Встретили закрывающую - вынимаем последнюю позицию из стека. Кусок от этой позиции до текущей - ваш блок кода.

    Этот алгоритм на вашем примере выведет именно то, что вам надо, но куски будут в обратном порядке, отсортированне по правому краю. Если надо сортировать именно как вы указали, то надо развернуть алгоритм - идите с конца и кладите в стек закрывающие скобки.

    Можно алгоритм усложнить, если надо какую-то проверку на синтаксис делать. Тогда вам придется, во первых, разбить текст на токены. Во вторых класть в стек всякие разные открывающие токены. Например, туда еще пойдут '[', '(' в С или "begin" в паскале. Потом при встрече любого закрывающего токена нужно проверить, что на вершине стека лежит правильный открывающий токен.

    Еще важно не забыть, что при некорректном входном коде у вас стек может кончится раньше времени. Например: "{a};}{". Тут, когда вы дойдете до второй скобки, у вас будет пустой стек. Это означает, что текст некорректен.
    Ответ написан
    Комментировать
  • Как решить задачу на or?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Есть решение, но оно на плохих тестах, если не повезет, не влезет в лимит.
    Чтобы вам повезло, можно "перемешать" входные данные случайным образом. Заведите случайную перестановку. Далее внизу я про нее как бы забуду, но когда я говорю спросить про элементы i и j вы должны справшивать про p[i] и p[j]. Как только вы найдете, что 0 в p[k] - про эту случайную перестановку можно забыть и далее использовать элемент p[k] для востановления перестановки.

    Спросите про элементы 0 и 1. Запомните a[0] | a[1].

    Вот у вас есть 2 элемента каких-то, вы знаете их позиции и a[i]|a[j]. Мы будет добавлять к ним элементы 2..n-1 по одному, все время выбрасывая один или несколько, в которых нуля точно нет.

    Вот есть у вас элеметы x, y и вы знаете a=x|y. возьмем новый элемент z и спросим b=y|z.

    Мог ли 0 быть в x? тогда бы a = x|y=0|y = y. Т.е. предыдущее значение a должно вкладываться в новое b как множество единиц. Аналогичный критерий для возможности z быть нулем - b вкладывается в a.

    Eсть 4 взаимоисключающих варианта, как множества бит a и b соотносятся:
    1) a целиком вкладывается в b, или a|b == b, a != b, например 5 вкладывается в 7.
    2) b целиком вкладывается в a, или a|b == a, a != b
    3) a == b
    4) ничего из выше перечисленного, например, 3 и 6.

    Мы уже становили, что x может быть 0 только в случаях 1) и 3). z может быть нулем только в случаях 2) и 3).

    В каких случаях может быть нулем y? только в 1), 2) и 4)

    Теперь алгоритм, если выпал случай 1), то мы выбрасываем z. При этом значение текущего OR уменьшилось как множество.

    Если выпал случай 2), то мы выбрасываем x. При этом значение текущего OR уменьшилось как множество.

    Если выпал вариант 3), то мы выбрасываем y и нам придется спросить x|z, ведь нам надо знать OR оставшихся элеметов. Это плохо тем, что мы спросили 2 раза, рассморев один новый элемент. В неудачном случае мы бы задали 2n вопросов только ища 0 и у нас не осталось бы квоты на восстановление самой перестановки.

    Но тут есть 2 подварианта. 3а) Если значение x|z стало меньше, как множество, то ок. 3б) Но оно еще могло бы остаться таким же, т.е. x|y=y|z=x|z. Но в этом случае, по аналогичным рассуждениям нуля не может быть нигде среди этих трех элементов! А значит мы можем выкинуть и их все, избавившись от двух элементов за два вопроса.

    Если выпал вариант 4), то мы выбрасываем x или z.

    Таким образом задав от n до 2n (в самом неудачном случае) мы получаем в конце 2 элемента в одном из которых точно 0, а второй - a.

    Чтобы восстановить кто из двух оставшихся элементов кто: по ходу отсеивания, задавая любой вопрос запоминайте для какой пары чисел вы видели нулевой бит в каждом разряде. (Т.е. спросили про i|j получили ответ 5. Тут второй бит 0, значит запомнили, что i|j дает 0 в бите 2. И поддерживайте массив для всех разрядов).
    Если вам хоть сколько-нибудь повезет. вы наберете достаточно таких примеров.

    Теперь пройдитесь по всем примерам, что вы ранее видели и выберите тот, который имел 0 в разряде, где у a стоит 1. пусть это были элементы i и j. Теперь спросите x|i. Если и там этот разряд 0, то x=0, иначе у=0.

    Тут почти нарисовался строго убывающий инвариант - размер множетсва бит в OR для текущих кандидатов, но пункт 3б) его ломает, ведь мы получаем 2 новых элемента и множество может стать любым. Но случайный порядок вопросов должен сделать этот вариант маловаероятным. У вас запас примерно 150 впоросов на максимальном тесте. Возможно тут можно придумать какую-то строгую схему, где какой-то инвариант строго убывает на лишних вопросах и может убывать не более 100 раз.
    Ответ написан
    Комментировать
  • Обход Binary-Tree, как сделать быстрее?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    У вас проблема в решении в том, что вы сравниваете 2 обхода. Это неверно для, как вам уже приводили в пример, этих двух деревьев:
    1
     \
      2
       \
        3
    
        3
       /
      2
     /
    1

    Тут оба обхода вернут 1,2,3.

    Можно в вашем решении добавлять всякие разделители при переходе влево-вправо-вверх. Вставить всякие yeild -1 и другие значения перед походом влево, выводом собственно элемента и походом вправо.

    Но есть лучшее решение, через DFS или BFS: вы делайте обход одновременно на двух деревьях. Т.е. у вас параметр функции dfs или в очереди в bfs лежат сразу 2 вершины - по одной из каждого дерева. Вы проверяете, что у них значения одинаковые и что у них у обеих одновременно есть или нет левого и правого ребенка. Потом вызываетесь/кладете в очередь одновременно левых детей и одновременно правых детей. Если в процессе обхода вершины оказались разными, или где-то есть ребенок, а где-то нет - то деревья не равны.

    Очень логично реализовывается в виде DFS (ниже псевдокод):
    Equals(t1, t2):
       // обработать случай null одного или обоих деревьев.
       return t1.v == t2.v && Equals(t1.left, t2.left) && Equals(t1.right, t2.right)


    Все предельно логично - 2 дерева равны, если у них равны корни, левые поддеревья и правые поддеревья.

    Кстати, ваше решение, если его исправить, тоже будет за O(n).
    Ответ написан
  • Как объединить массивы с дубликатами в уникальные списки?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Я, правда, не понимаю, почему нельзя просто объединить все массивы в один (будет просто список уникальных элементов). Возможно вам нужно максимальное количество массивов.

    У этой задачи есть два варианта решения - через обход графа или через систему непересекающихся множеств. Но они оба по сути одно и тоже, просто разные реализации.

    Суть в том, чтобы построить граф, где все различные элементы всех массивов будут вершинами. Ребра есть между двумя вершинами, если соответствующие им элементы присутствуют в одном и том же входном массиве.

    Ответ на вашу задачу - компоненты связности этого графа. Вариант через граф следующий - для каждого массива перебирайте двумя циклами все пары элементов и добавляйте ребро в граф. Используйте какой-нибудь ассоциативный массив списков для сохранения списка смежности. Или используйте ассоциативный массив для назначения каждому уникальному элементу уникального порядкового номера. Потом список смежности или матрицу инцидентности можно хранить просто по номерам, в виде массива списков или двумерного массива. Потом обходом в глубину или в ширину найдите все компоненты связности.

    Чуть проще и быстрее будет работать решение через систему непересекающихся множеств (DSU). Сначала пройдитесь по всем массивам и, как и в первом решении, перенумеруйте все уникальные элементы. Заведите DSU, где каждый элемент - сам себе множество.

    Потом пройдитесь по всем элементам каждого входного массива и объединяйте его множество с множеством предыдущего элемента в том же массиве. Попутно, можете считать количество объединений, которые выполнились (множества были различными до выполнения операции union_sets). Так вы можете подсчитать, сколько массивов будет в ответе. В итоге все элементы для каждого массива в ответе будут в одном и том же множестве. Теперь, чтобы вывести ответ заведите массив списков с известным количеством элементов. Используйте ассоциативный массив, чтобы назначить каждому множеству один из списков в ответе. Пройдитесь по всем элементам и дописывайте их в соответствующий список.
    Ответ написан
    Комментировать
  • На плоскости расположены n предметов, их нужно переместить в заданные n позиций. Как это сделать?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Как вам уже написали, очень просто получить матрицу нужного времени для перемещения каждого предмета в каждую точку по отдельности (расстояние по теореме Пифагора делить на скорость объекта). Теперь вам надо найти такое минимальное время, что для каждого предмета можно выбрать уникальную точку, до которой время перемещения не больше заданного.

    Отсортируйте все N^2 времен. Запустите по ним бинарный поиск. В качестве проверки запускайте алгоритм Куна для поиска максимального паросочетания, если оно оказалось полным - текущая граница подходит или слишком большая. Если максимальное паросочетание не полное - граница слишком мала.
    Это решение за O(N^3 log N)

    Альтернативное решение - отсортируйте все времена и добавляйте соответсвующее паре точек ребро в граф. Ищите дополняющий путь (обход в глубину из алгоритма куна). Если нашли - расширяйте паросочетание. Как только паросочетание стало полным, последнее добавленное - ребро ваш ответ. Это решение за O(N^4). Для N<50 тоже подходит.

    Нужно знать: Графы, обход в глубину, паросочетание в двудольных графах (алгоритм Куна, например), бинарный поиск.
    Ответ написан
    3 комментария
  • Вопрос по оценке сложности алгоритма?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Видимо, тут считается сколько раз эта строка выполнится суммарно, а не сколько она сама занимает.
    Т.к. эта строка в цикле, который повторяется O(n) раз, то и строка эта (которая выполняется за константное время) всего займет O(n).
    Ответ написан
    Комментировать
  • Можно ли в игре создать объект со случайными и зашифрованными координатами?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно после сразу генерации координат захешировать их и забыть про них. После, при прорисовке в каких-либо координатах хешируете их также и сравниваете с имеющимеся значениями. Если совпало, рисуете.

    Но тут есть проблемы - работает только в ограниченных случаях типа 2d сетки, где можно на каждый кадр перебирать все клетки и смотреть, если там объект. Во вторых, хакер все еще может перехватить генерацию координат или вообще отреверсить алгоритм хеширования, вытащить хеши и брутфорсом подобрать координаты с такими хешами.
    Ответ написан
    Комментировать
  • Быстрая сортировка на Golang, почему правая сторона не сортируется?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Еще ошибка в этой строчке:
    p := A[k/2]

    Вам надо выбрать какой-то (видимо, средний) элемент между b и k. Если b достаточно большое, то k/2 вылезет за границу сортируемого куска. Для среднего надо писать (b+k)/2.
    Ответ написан
  • Сопоставление матриц?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Рекурсивной функцией делайте. Передавайте ей уже собранную строку и сколько элементов списка rates обработано. В функции перебирайте каждый элемент внутреннего rates дописывайте к уже собранной строке один элемент и рекурсивно запускайтесь дальше. Если все элементы обработаны, добавляйте собранную строку в ответ.
    Ответ написан
  • Определить начло и конец скобок из String и расставить правильном порядке?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Я так понял, у вас в строке скобки расставлены правильно, и вам надо поставить соответствие межу скобками.

    Используйте стек. Пройдитесь по строке циклом. При встрече открывающей скобки кладите ее позицию в стек. При встрече закрывающей скобки доставайте позицию соответствующей ей открывающей из стека. Скобки расставлены неправильно, если после обработки всей строки в стеке что-то осталось или вы встретили закрывающую скобку при пустом стеке.
    Ответ написан
    Комментировать
  • Правильные ли ответы в рамках составления алгоритма?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В зависимости от уровня задающего задание, ваш ответ правильный или не совсем правильный.
    Да, именно if-ов сравнений 2N. Но внутри цикла for тоже закопано N сравнений. Как цикл-то работает? У него есть переменная счетчик и он ее сравнивает с граничным условием. Это хорошо видно на блок схеме - блок-цикл имеет 2 выхода. Значит в нем есть ветвление и, следовательно, сравнения.

    Так что более точный ответ 3N. Еще более точный ответ 4 N. Потому что функция readln тоже должна какие-то сравнения делать, чтобы остановить ввод на конце строки, но это уже nitpick, который от вас почти точно не ожидается.
    Ответ написан
    2 комментария
  • Как выполнить свертку сочетаний?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Странно задача сформулирована, но, кажется, надо просто найти все уникальные числа во входных данных.
    Или какой-нибудь set используйте, или сложите все входные числа в массив, отсортируйте и потом удалите повторяющиеся подряд идущие элементы.
    Ответ написан
    Комментировать
  • Как понять условие задачи?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Про модули уже отвечено, я расскажу, как решать эту задачу быстрее, чем полным перебором.

    Пусть общее количество карт - N.

    Для простоты понимания, давайте забудем про колоду, просто карты раздаются как-то в 2 кучки по N/2 карт. Это пригодится попозже. Удобнее не вероятность считать, а количество способов раздать карты. Потом надо будет поделить количество хороших способов на все способы.

    Сразу комбинаторно подсчитаем, сколько всего способов есть. Это просто количество всех различных колод. Гуглите "перестановки с повторениями". Формула будет такой:
    N!/ a[1]! ... a[n]!
    Раз мы на это число в конце будем делить, то сразу же считайте перевернутую формулу: подсчитайте факториал числа всех карт по модулю, обратите, умножьте на все маленькие факториалы.

    Теперь самое главное, подсчитаем количество способов раздать карты так, что у всех поровну уникальных карт.
    Динамическое программирование DP(k, t, i, j) - сколько способов раздать первые k типов карт так, что у первого игрока t карт всего, из них i уникальных, а у второго игрока j уникальных карт (при этом мы знаем, сколько у него всего карт - a[1]+...+a[k] - t).

    База:
    DP(0,0,0,0) = 1,
    DP(0,*,*,*) = 0


    Ответ:
    sum {i=1..n} DP(n, N/2, i, i)

    Пересчет:
    DP(k,t,i,j) = sum{v=0..min(a[k],t)} DP(k-1, t-v, i - {v>0}, j-{v < a[k]}) * C(t, v) * C(a[1]+a[2]+...+a[k]-t, a[k]-v)


    Тут надо объяснить: мы перебираем, сколько карт последнего типа досталось первому игроку: v. Это все разные колоды, поэтому их надо суммировать. Как бы убираем все карты этого последнего типа из рассмотрения. Что остается? То же самое ДП, но типов на 1 меньше, у первого игрока на v карт меньше и уникальные карты надо уменьшить на 1, если какому-то игроку хоть одна карта досталась. Но есть еще множители - это количество сочетаний. Эти самые v карт последнего типа могут быть на любом из t мест в колоде у первого игрока. Аналогично для второго игрока. Тут надо умножать, потому что любую колоду из предыдущего состояния ДП можно разбавить картами последнего типа в разных позициях и все эти способы дадут разные результирующие колоды. Надо домножить на оба сочетания, потому что мы независимо можем тасовать карты у первого и второго игрока.

    Сочетания считаются факториалами С(n,k) = n!/ k! (n-k)!.
    Я бы посоветовал предподсчитать все факториалы до 500 по модулю в массив, а так же обратные к ним.

    Еще, если не будет влезать по памяти, обратите внимание, что в ДП достаточно хранить лишь 2 слоя по первому параметру. Т.е. надо места для 2*500*50*50 элементов, что для 4-х байтных значений будет занимать ~10mb памяти. Может даже long long можно хранить. Но тут надо переписать ДП снизу вверх, а не рекурсивно. Просто посмотрите, какие состояния куда плюсуются и с какими множителями. В этом случае вы будете не убирать карты последнего типа, а добавлять карты нового типа. Опять же, перебирайте сколько кому этих карт достанется. Надо только осторожно смотреть множители - С(сколько карт после добавления, сколько добавили).

    Теперь прикинем на коленке сложность вычислений таким алгоритмом.
    Само ДП имеет состояний 50*500*50*50 и пересчет 11 вариантов. Если все перемножить, получается что-то меньше 700 миллионов - в одну-две секунды должно влезать.

    Полный перебор у вас, занимает что-то типа 11^50 - никогда не дождетесь на сколько нибудь не тривиальном тесте.
    Ответ написан
    21 комментарий
  • Какие есть алгоритмы сжатия числа?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно хранить в битовом массиве, тогда потребуется ровно 10 бит для каждого числа. Каждые 4 числа занимают 5 байт.

    Будет небольшое замедление при записи и считывании чисел из такого массива (Надо найти смещение, прочитать 2 каких-то байта, отбросить лишние биты)

    Возможно последний байт в массиве не до конца использован (+6 лишних бит), когда как в текущем решении у вас лишние 6 бит на каждое число.
    Ответ написан
    2 комментария