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

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

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

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

    Смотрите на ограничания - n и m до 100000. Обычно такой порядок чисел означает, что вам нужно решение за O(n+m) или что-то вроде O(n+m log (n+m)). У вас же решение "в лоб" за O(nm), что никак не ускорить принципиально. Даже переписыванием на си со всякими хитростями вы ускорите его в 10, ну в 100 раз. А вам надо ускорить его в 10000 раз.

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

    Остался случай - что если один массив целиком отрицателен, а второй - целиком положителен. Пусть a положителен, а b отрицателен. Что будет, если взять минимальные по модулю элементы и там и там? Посмотрите внимательно - вам дано, что ни одно из чисел не 0. Это поможет.
    Ответ написан
  • Можете решать эту задачу?

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

    Воспользуйтесь алгоритмом тарьяна: https://e-maxx.ru/algo/lca_linear_offline для получения ответов на все запросы LCA сразу.

    Сначала при вводе создайте дерево целиком и запомните все запросы Get в массив. Потом пройдитесь по массиву запросов и раскидайте их в списки на вершинах, как надо в алгоритме таръяна.

    Вам надо лишь чуть-чуть подкорректировать этот код, чтобы вместе с вершиной-запросом добавлять в списки к вершинам номер этого запроса. И тогда вместо вывода v+1, q[v][i]+1 вы сможете записать ответ в массив по нужному индексу.

    Ну или воспользуйтесь онлайн алгоритмом для поиска LCA через обход со временем входа/выхода и деревом отрезков на минимимум.
    Ответ написан
    Комментировать
  • Как можно ускорить алгоритм?

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

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

    Если вы не нашли делителя до корня, то число простое и в качестве делитеял можно брать все K и тогда ответ будет K-1.
    Ответ написан
    2 комментария
  • На сколько критичен сдвиг в массиве для алгоритмов?

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

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

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

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Как выяснили в комментариях, метрика будет sum_i w[i]^2 (actual[i]/ needed[i] - 1) ^ 2.

    пусть x[i] - соклько i-ого продукта
    Пусть a[i][j] - сколько в еденице i-ого продукта j-ого ингредиента. Тогда всего j-ого ингредиента будет sum_i a[i][j] x[i].

    Тогда получаем следующую оптимизационную задачу:

    Sum_j (sum_i A'[i][j] x[i] - C[i]) ^ 2 -> min
    sum_i x_i = 1
    x_i >= 0

    Сумма всего приравнивается к 1, потому что иначе надо считать пропорции и получится вообще ужас-ужас.

    A'[i][j], C[i] - какие-то числа, которые элементарно получаются из a[i][j], w[i] и needed[i].
    A'[i][j] = a[i][j]*w[j]/needed[j]
    C[j] = w[j] / needed[j];

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

    Можно применить метод лагранжа, а точнее метод Каруша — Куна — Таккера

    Сначала выразите x_1 = 1 -sum_j>1 x_j, подставьте во все уравнения и получите:
    Sum_j (sum_i A''[i][j] x[i] - C'[i]) ^ 2 -> min
    sum_i x_i -1 <= 0
    x_i >= 0

    Это уже похоже на то, что есть в википедии. Тут одно условие, поэтому будет один множитель лямбда. Надо составить функцию лагранжа, взять все ее производные по x_i и приравнять к 0. Полутчится система линейных уравнений.

    Для условия жесткости придется рассмотреть 2 случая - лямбда - 0 или тогда sum_i x_i - 1 = 0

    Прорешать оба случая (СЛАУ) и взять те, где все получается положительным.

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

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

    Можно итеративно - перебирайте число от 0 до 2^|количество чисел|. Биты в этом числе будут обозначать, берется ли конкретный элемент множества в сумму. Дальше считайте сумму текущего подмножества пользуясь бытовыми операциями, чтобы проверить стоит ли заданный бит в 0 или 1. Если набрали половину общей суммы - выводите те индексы, где биты 1 как одно множество, а оставшиеся - как второе. Чтобы каждое разбиение не выводить 2 раза, требуйте, чтобы старший бит был 0 (перебирайте до 2^|количество чисел-1|)

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

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

    Можно вместо рекурсии использовать стек вручную.

    Edit: а вручную будет примерно так: Читаем бит. Если 0, то кладем в стек 0. Если 1, то текущий стек - код симола по следующим 8 битам и удаляем из стека все 1 сверху до первого нуля. Нуль меняем на 1.
    Правда это даст не дерево а только коды символов. Чтобы получить дерево надо будет в стеке хранить указатели на вершины.
    Ответ написан
  • Как найти все завершённые пути на карте?

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

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

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

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

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

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

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

    А дальше вам надо посчитать длины трех маршрутов и выбрать из них лучший.
    Ответ написан
    Комментировать
  • Как реализовать алгоритм экспайринга элементов в базе данных?

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

    По нормальному, это должен быть один поток с приоритетной очередью, который работает постоянно.

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

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

    Обычно такое реализуется через какой-то примитив событий: не знаю, что там есть в go. Должна быть функция ожидания события с таймаутом. Вот там надо ждать события, которое выстреливает при добавлении новой таски пользователю, а таймаут должен браться из приоритетной очереди.

    При падении этого потока его можно перезапустить. При старте он должен получить из базы данных пока не истекшие таски и сложить их все в очередь и сразу удалить все с истекшем сроком.
    Ответ написан
    Комментировать
  • Как понять в коде сложность алгоритма?

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Проблема в том, что "214" - это строковая константа. Поэтому char *num = "214"; инициализурует указатель num адресом этой константы. Сама эта константа, видимо, лежит в read only памяти. Поэтому когда вы в алгоритме попытаетесь что-то в массив записать, программа упадет.

    Если вы внимательно почитаете вывод компилятора (желательно с ключем "-Wall"), то он вам про это выдаст варнинг:
    main.cpp:54:17: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]


    Надо делать так:
    char num[] = "214";

    Ну еще у вас там в коде ошибка с непроставленным где надо +1 у index. Разворачивать надо кусок правее только что увеличенного элемента. А вы разворачиваете вместе с ним. Поэтому ваша программа еще и повиснет.

    Ну и первую перестановку ваш код не выводит.

    И последнее, вместо for (; index != -1;) лучше использовать while (index != -1)
    Ответ написан
    2 комментария
  • Как вернуть первые N максимальных элементов из массива без сортировки массива?

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

    Пусть у вас N элементов в массиве и надо вернуть K минимальных. Тогда сортировка будет работать за O(N log N), а quickselect за O(N) в среднем*. В худшем случае может быть и квадратичное время работы, но этот случай практически невозможен, если реализация испольует случайные числа для выбора ведущего элемента.

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

    Суть его в том, чтобы в куче (heap aka priority queue) поддерживать пока найденные K минимальных элементов. При этом куча будет на максимум. Сначала туда кладутся первые k элементов массива, а потом каждый следующий вытесняет максимальный элемент в куче, если он его меньше.

    * Вообще говоря, можно заставить quickselect и quicksort работать идеально всегда, если использовать алгоритм Блюма-Флойда-Пратта-Ривеста-Тарьяна, который ищет медиану за O(n). Но на практике этот алгоритм не применятеся, потому что у него такая константа, что там на логарифм хватит и еще на квадрат останется.
    Ответ написан
    2 комментария
  • Как в проходе графа DFS создать массив маршурутов до нужной вершины?

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

    При выходе из функции очищайте пометку visited. При входе добавляйте вершину в массив/стек/список. Там будет храниться пройденный путь. При выходе из функции - убирайте последнюю вершину из пути. Если зашли в конечную вершину - выводите этот стек с вершинами.

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

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

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

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

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

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

    Сначала читаете первую строку и создаете из нее корень дерева.

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

    В вашем примере.

    Прочитали иван, сделали корень дерева. Вектор будет {"иван"} (указатель на вершину-корень дерева).

    Прочитали алексей. 1 проблел - все хорошо, в векторое 1 элемент, значит может быть до 1 пробела. Добавили "алексей" к вершине "иван". Вектор {"иван", "алексей"}

    Прочитали игорь. Вектор стал {"иван", "алексей", "игорь"}.

    Прочитали андрей. 1 пробел. Может быть до трех пробелов, ведь в векторе 3 элемента. Добавили "андрей" к узлу из вектора по индексу 0 (-1 к количеству пробелов). обрезали вектор до длины 1 и добавили туда новую вершину. Вектор стал {"иван", "андрей"}

    И т.д.

    Для добавления вершины создайте новый TreeNode с нужной строкой и push_back в список детей для нужного отца.
    Ответ написан
    Комментировать