Ответы пользователя по тегу Алгоритмы
  • Задача о рюкзаке, используя 1-D массив?

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

    Этот трюк не всегда возможен. Например, если вам надо не только максимальный вес/цену найти, а еще и набор предметов. Восстановление ответа не всегда можно сделать лишь по последней строке.
    Ответ написан
  • Как найти наикратчайшие пути взвешенного орграфа, представленного матрицей инцидентности, используя алгоритм Дейкстры?

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

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

    Кстати, у вас матрица инцидентности неправильная: должно быть по 2 числа в каждой строке. Обычно ставят -цену у начальной вершины и +цену у конечной.
    Ответ написан
    4 комментария
  • В чем проблема в коде работы с графом?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Ошибка в том, что у вас граф криво задан. У вас там ровно 6 списков. А размер графа - 7. Поэтому в MakeStep вы обращаетесь к graph[6], а это undefined behavior. А там непонятно, что происходит. Может вы таблицу портите каким-то значениями и никогда положительного значения не получаете. Скорее всего корраптите память через обращение к мусорным adj в std::array (который на стеке лежит) и получаете мусор в каких-то локальных переменных.
    Ответ написан
  • Что не так с кодом, проверяющим логическую схему?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Во-первых, у вас цикл от 0 до 32 включительно. Т.е. вы на последней, 33-ей итерации пытаетесь преобразовать 0b100000 в bitset, фактически, второй раз учитывая вариант со всеми нолями. Вот почему вы получаете 33 а не 32.

    Во-вторых, у вас ошибка с тем, что вы из bitmask берете биты, которые есть числа 0 и 1 и проводите над ними битовые операции, а не логические. И это у вас там 32-битные числа же! Для | и & оно еще совпадает с вашими ожиданиями, а вот ~ обращает ВСЕ 32 бита числа.

    Поэтому ~(bitset[E] | bOrD) всегда выдаст или -1 или -2. Вы потом это пропустите через or, получите опять же -1 или -2 и в конце преобразуете это в bool. И вот тут-то оно всегда и станет true.

    Чтобы это исправить, или используйте логические операции (||, &&, !), или вместо ~x используйте x^1, или в самом конце возвращайте результат с &1, чтобы значения остальных бит ни на что не влияли.
    Ответ написан
    5 комментариев
  • Возникает ошибка, но не знаю какая?

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

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

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

    Это отработает за 3^15*15 = 215233605 операций. Обычно это проходит. Можно соптимизировать: не брать текущую монету, если она слишком большая, останавливать перебор, если сумма первых монет недостаточна. Ну, или соптимизировать до 2^15*15: подсчитайте все возможные суммы, если можно брать по 1 монете. Таким же перебором или вообще циклом с битовой маской. Отсортируйте список из 32 тысяч чисел и проверьте, а есть ли там 2 числа, дающие искомую сумму (двумя указателями: двинули один раз левый, двигаем правый пока сумма слишком большая).

    Отдельно надо проверить, надо ли выводить -1 в ответ (сумма всех монет меньше N).
    Ответ написан
    Комментировать
  • Проверка редких кейсов в логике игр?

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

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

    Картинке удовлетворяет такая интерпретация: Берется область, остекаемая прямыми, содержащая центр квадрата. Предполагаем, что прямые через центр не проходят.

    Тогда каждая прямая задает полуплоскость допустимых точек, да и сам квадрат можно задать 4-мя такими полуплосокстями и вам надо найти пересечение всех их.

    Эта задача решается за O(n log n). Задайте каждую прямую в виде ax+by+c=0. Т.ч. вектор {a,b} выпущенный с прямой, торчит в сторону полуплоскости, которую надо взять. Если вдруг не так, то надо поменять знак у всех трех коэффициентов.

    Разбейте все прямые на 2 множества, те - которые смотрят "вверх" и те, которые смотрят "вниз". Если вектор нормали имеет положительную y координату - это смотрит вверх. Вертикальные прямые можно включить, допустим, в верхнее множество.

    В каждом множестве построим ломанную, отделяющую нужную область. Это будет выпуклая область с двумя бесконечными лучами в конце.

    Потом отсортируйте все прямые по углу наклона вектора {a,b}. Это можно сделать без тригонометрии, сравнивая векторы по векторному произведению. Ну, или просто какую-нибудь функцию вроде atan2() используйте.

    Потом будем поддерживать ломанную, описывающую пересечение первых k проуплоскостей. Изначально это просто одна прямая (для первой полуплоскости). Будем хранить это как стек из прямых и рядом стек из точек пересечения. Изначально там только одна прямая и 0 точек пересечения.

    Потом добавляем полуплоскости по одной. Пока последняя вершина на ломанной не лежит в нужной полуплоскости, выбрасываем ее. Для этого убираем из обоих стеков последний элемент. Это можно проверить, просто подставив координаты точки в уравнение ax+by+c. Если даст отрицательное значение - выбрасываем.
    Если точек не осталось, или точка лежит где надо, то новую прямую надо пересечь с последней прямой. Точку пересечения и новую прямую добавляем в стек.

    В конце мы получили две ломанные, огибающие нужное пространство сверху и снизу. Надо их пересечь.
    Для этого выкинием сверхней ломанной все крайние точки, которые отсекаются последней и первой прямой в нижней ломанной. И наоборот. В конце, пересечения двух бесконечных лучей слева и справа добавляем в ответ.
    Ответ написан
    Комментировать
  • C++: Как ускорить этот многопоточный код?

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

    В самом конце можно списки объединить. Последовательно. Или с критической секцией. Каждый поток должен получить первый и последний элемент списка. За две операции добавить список к глобальному и изменить последний элемент в глобальном списке. Ну, это если std::list использовать.

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

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

    Надо будет построить граф. Взять все точки доставки и склад как вершины и каким-нибудь google maps найти расстояние и пути между каждой парой точек. Эти длины пути надо записать в ребра.

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

    Если у вас точек и куръеров действительно мало (не более 25 точек, не более 3 куръеров), то можно попробовать решать через динамическое программирование F(M, c1, c2, c3) - минимальная стоимость развезти товары из множества M так, что 3 куръера остаются в вершинах c1, c2 и c3. Переход - перебрать куръера и откуда он пришел из множества M. Посмотрите в википедии алгоритм для одного куръера, на трех это легко обобщается. Будет это работать за O(n^(c+1)2^n), где c - количество куръеров. n - количество вершин в графе. Сильно много куръеров или точек на карте этот алгоритм не переварит.
    Ответ написан
    Комментировать
  • Алгоритм для преобразование матрицы в треугольную?

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

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В вашем рассуждении ошибка в том, что вы не домножаете условную вероятность 2/6 на вероятность условия. Вы же полную вероятность считатете а P(A) = P(A|B)*P(B)+P(A|!B)*P(!B). У вас P(A|B) = 0, как вы заметили - если они встретились в первом туре, то во втором уже не встретятся. Но все-равно надо 2/6 домножать на P(!B) - вероятность того, что они не встретились в первом туре. А это 1-1/7 = 6/7. В итоге получается все то же 2/6*6/7=2/7.

    Но рассуждения автора проще. Можно рассмотреть все независимые элементарные исходы "где в сетке оказался второй игрок". Их 7, они равновероятны по симметрии. Дальше надо домножить на вероятность, что они начиная так встретятся (выиграют свои матчи).
    Ответ написан
    4 комментария
  • Почему 3 секунд не хватает для выполнения кода?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    У вас решениe за O(n^3), ибо у вас там 2 вложенных цикла до n, а внутри еще и постоянно вызываются min/max, которые проходятся по всему массиву.

    Ограничения же в задаче n<10^5. С такими ограничениями максимум O(n log n) решение уложится в 3 секунды.

    Подумайте, как его можно изменить, чтобы работало сильно быстрее? Подсказка: сначала вы берете 1 минимальный элемент, потом 2 самых маленьких, потом 3, и т.д. На второй итерации вам уже как бы не надо искать минимум- вы его уже знаете. Вас интересуют только оставшиеся числа. На третьей итерации у вас уже 2 числа раньше найдены. Надо как-то переиспользовать предыдущие вычисления.

    Что можно сделать с входным массивом, чтобы можно было получать несколько самых маленьких элементов быстро? Помните, что вам надо уложиться в O(n log n).
    Ответ написан
    Комментировать
  • Как найти компоненты связности в графе в распределенной памяти?

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

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

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

    Более сложный вариант - когда у вас сразу запускаются все волны. Опять же, без лидера совсем сложно будет.
    Тут, наверно стоит сначала в каждом отдельном параграфе найти все компоненты связности отдельно. Это будут промежуточные варианты.

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

    Когда сервер получает сообщение о номере по призначному ребру, если этот номер меньше текущего у компоненты, она перекрашивается. И сообщение рассылается по всем остальным призрачным ребрам, торчащим из этой компоненты. Рано или поздно, все вершины одной компоненты по всем серверам будут помечены минимальным номером.

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

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

    Потом его надо вывести, вставляя только необходимые скобки. Надо порисовать на бумаге кучу случаев и аккуратно их разобрать.

    Так, если вершина "+", то скобки ставить вообще не надо. Если вершина "*", то надо ставить скобки вокруг ребенка, если там операция "+" или "-". Если вершина "-", то надо ставить скобки справа, если там "+" или "-". Если вершина "/", то надо ставить скобки вокруг левого сына, если там "+" или "-". Вокруг правого сына скобки надо ставить если не число. Подумайте аккуратно, может я какие-то правила упустил.

    Это удобно реализовывать рекурсивной функцией. Она проверяет, надо ли ставить скобки вокруг левого сына и выводит его рекурсивно, потом операцию, потом также правого ребенка.

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Обе книги классные. Мне Кормен показался попроще и по-понятнее. Кнут покрывает больше тем. Я бы начал с Кормена.
    Ответ написан
    1 комментарий
  • Как нарисовать кривую Серпинского (см. ниже), не используя графические библиотеки, а '*' или слешы?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Напрягает требование, что нельзя использовать контейнеры. Так-то ваш подход правильный: Заводим поле для вывода, рекурсивной функцией, которой передаются порядок кривой, и где ее рисовать (квадрат и его поворот). Функция рекурсивно вызывает 4 кусокчка в каждом из 4 квадратов и рисует 3 соединительных кусочка. Но поле для вывода это так или иначе массив. Можно и самому его завести, но почему нульзя использовать контейнеры - не понятно.

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

    А потом циклом выводим результат работы функции для всех координат. Это работает в логарифм раз медленее, но зато не требует выделения памяти под все поле вывода.

    edit: Да, еще есть трюк - считайте, что кривая не замкнута. Левый верхний угол - пустота. И надо отдельно в самом конце замкнуть ее в этом углу через "/".
    Ответ написан
    6 комментариев
  • Как из первых N натуральных чисел составить максимальное количество пар, суммы которых являются простыми?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    В вашем решении есть ошибки: Вы считаете сумму двух чисел от 0 до N, а не от 1 до N.

    А главная проблема вашего решения в том, что оно "жадное". Вы жадно берете первую папавшуюся пару, как будто так будет оптимально. Но это не так. Например, если у вас остались гири весами 2, 3, 4, 5, то брать пару 2+3 = 5 нельзя, ведь тогда оставшиеся 4+5=9 дадут не простое число.

    Это действительно можно решать через задачу о паросочетании как сказал Alexandroppolus. Вот только все алгоритмы работают за O(N^3), так что для N=500000 это решение будет работь очень долго.

    Но в вашем случае граф очень специфичный, так что надо придумать какой-то шаблон. Например, можно взять максимальное простое число P <= N+1 и набирать его всеми возможными парами (1+(P-1), 2+(P-2)). В итоге у вас остнется одна нечетная гиря (P+1)/2 и все гири >=P. Например, если N+1 простое - то это оптимальный способ.

    Я бы посоветовал написать решение через максимальное паросочетание и прогнать его для всех N <=200. Получите оптимальные ответы, посмотрите на них. Поищите какой-то паттерн в количестве пар, потом попробуйте придумать способ такое количество набрать.

    И еще немного покритикую ваш код.
    f==true. Зачем? Можно написать if(f). А вообще вам тут f не нужно, пишите if(Check(i+j)).

    Вместо
    if(a[j]!=0&&a[i]!=0&&i!=j) { 
      ...
    }

    стоит использовать "ранний выход":
    if(!a[i] || !a[j] || i == j) continue; 
    ...


    Так вложенность и сложность кода меньше. Его проще читать и понимать.

    Вместо прверки, что i != j, можно внутренний цикл гнать от n до i+1. Вам же не надо перебирать отдельно пары 1+10 и 10+1? Всегда считайте, что первое число меньше второго в паре.

    Ну, и отступы, ради всего святого, расставьте аккуратно. Любой редактор кода умеет их делать автоматически.

    Edit:
    Совместо с Alexandroppolus в комментариях придумали следующее решение: Берете минимальное простое число, большее N. Обзовем его P. Тогда берем пары N+(P-N), (N-1)+(P-N+1)... и т.д. В этих парах одно число четное, другое нечетное. И всего они покроют отрезок четной длины без дырок. Потом задача сводится к такой же, только уже с максимальным числом не N, а P-N-1.
    Эта жадность работает, потому что она всегда соберет максимально теоретически возможное количетсво пар. Может только одна 1 в конце останется.
    Ответ написан
    6 комментариев
  • Как найти минимальный ограничивающий параллелепипед?

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

    Кажется, есть эффективное решение через численную оптимизацию и троичный поиск.

    Введем функцию f(a,b) - минимальный объем параллелипипеда, повернутого на угол a вдоль оси oz и потом угол b вдоль ox. Ну или это эйлеровы углы, если хотите.

    Вам надо найти минимум этой функции. Утверждение: если зафиксировать a, то двигая b функция будет переодическая с двумя экстремумами. Ну, потому что поворот на 90 градусов взвращает все как было, только оси местами меняются. Значит, на ней можно что-то вроде тернарного поиска запускать (об этом дальше).

    Если же ввести функцию g(a) =Min_b(f(a,b)), то она, похоже будет такая же. По ней тоже можно такой же поиск запустить.

    Итого, 2 вложенных тернарных поиска, в внутри легче все точки повернуть на -b и -a градусов и потом взять обрамляющий параллелипипед, параллельный осям координат (min/max по трем координатам за 1 проход).

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

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

    Это будет что-то вроде O(n log^2 n).

    Или можно просто случайным образом или с малым шагом перебирать разные углы поворота. Поворачивать все точки и искать параллельный осям координат параллелипипед (просто беря min/max по трем координатам). Для 800 точек можно 10000 углов перебрать и это займет лишь 100мс на современном железе.

    Это сильно проще в реализации и, хоть и не найдет самый оптимальный параллелипипед, на глаз будет сложно это заметить.

    Edit: вообще, кажется, там 3 угла, а не 2. Ну появляется еще третья функция t(a,b,c). и лишний Log n в сложности. Перебирать углы становится еще хуже, но все еще возмоно.
    Ответ написан
    Комментировать
  • Какая space complexity у данного алгоритма?

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

    У этого алгоритма действительно пространственная сложность O(N) (если N - размер входного массива). Однако, можно натянуть сову на глобус, если отдельно считать входные и выходные данные (которых O(N)). Тогда можно сказать, что алгоритм использует O(1) дополнительно пространства. Входные и выходные данные все равно понадобятся, поэтому иногда их не считают.

    У меня нет доступа к этому видео. Если они там говорят "additional space is O(1)", то это именно так, как я описал выше.
    Ответ написан
    1 комментарий