Ответы пользователя по тегу Алгоритмы
  • Алгоритм работает не правильно?

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

    Вы же как-то пытаетесь реализовать это только лишь с переменной mid. Если надо идти влево, то вы делите mid пополам, как будто бы текущий отрезок от начала массива (Но это не всегда так: после первого же шага вправо начало массива будет уже выкинуто из расмотрения), Потом, при переходе вправо, у вас какой-то бред написан.
    Math.floor((arr.length - mid) / 2) - это что вообще должно делать? Если mid=9, length = 10, вы вообще уйдете в начало массива, хотя должны идти вправо. Если вы хотели взять середину между mid и length, то там должен стоять "+" внутри.

    Но так все-равно не получится сделать. Заведите 2 переменные l и r, как во всех реализациях бинпоиска, считайте mid как середину отрезка, и при выбрасывании одной из половин просто переписывайте l или r на mid+1 или mid-1 (потому что сам mid элемет вы уже рассмотрели и он точно не нужен).
    Ответ написан
  • Как исправить скобочную последовательность?

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

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

    База - если l..r - пустая строка - ответ 0.
    Иначе надо рассматривать варианты, что будет с последним символом. Если в конце стоит открывающая скобка, то ее надо удалить - других вариантов нет: F(l,r) = 1+F(l,r-1).

    Если же там закрывающая скобка, то есть 2 варинта: или этот символ удаляем, или берем в ответ. В первом варианте ответ такой-же, как выше. Во втором - надо перебрать, а какой же символ в строке будет открывающей скобкой для данной. Пусть это символ i (там должна стоять открывающая скобка того же типа). Тогда ответ F(l,i-1)+F(i+1,r-1) - ведь части перед парой скобок и внутри их должны тоже быть правильными последовательностями.
    Из всех вариантов надо выбрать минимальный - это и будет ответ для текущего состояния.

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

    Ответ к задаче - F(1,n) - для всей строки.

    Это решение потребляет O(n^2) памяти и занимает O(n^3) времени.
    Ответ написан
    2 комментария
  • Алгоритм для минимизации стоимости товаров от разных поставщиков, какие ресурсы изучить?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Можно свести к линейному целочисленному программированию (linear integer programming).

    Индикаторные переменные (0 или 1) - покупаете ли вы этот товар у этого продавца (x_ij).
    Еще переменные - платите ли этому продавцу за доставку (y_j).

    Ограничения:

    y_j >= x_ij
    sum_j (x_ij) = 1

    Целевая функция - мнинимизировать затраты: sum_ij x_ij*c_ij + sum_j y_j*d_j

    Потом решать каким-либо солвером (есть куча быстрых библиотек).

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

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

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

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    У вас решение за O(N^2), когда как можно сделать решение за O(N). Тут не в питоне дело, а в плохом алгоритме.

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

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

    Я не совсем питонист, но вот примерный код:

    def days_till_warming(T):
        counts = []
        stack = []
        for i, curr_temp in enumerate(reversed(T)):
            while len(stack)>0 and stack[-1][0] <= curr_temp:
                stack.pop()
            delta = i - stack[-1][1] if len(stack) > 0 else 0
            stack.append([curr_temp, i])
            counts.append(delta)
        return list(reversed(counts))
    Ответ написан
    Комментировать
  • Что получится в результате выполнения блок-схемы?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Первый цикл читает входные данные в массив x. Ваши "1, 1, 1, 0, 0, 1, 0, 1, 1" будут в этом массиве (только нужно сначала 9 ввести в качестве n).

    Поэтому проверка на 1 имеет смысл и даст истину для первых трех итераций, но не для двух следующих, и т.д.
    Ответ написан
  • Как выполнить проверку if в функции только 1 раз при первом вызове?

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

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

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

    Надо поддерживать массив позиций по по всем айдишникам. При любом перемещении пары элементов в массиве в очереди надо обновлять этот массив позиций.

    При изменении приоретета уже не надо никакого find, потому что позиция уже будет доступна в массиве.

    При добавлении нового элемента не надо вызывать build_heap. достаточно только shift_up от нового элемента. У вас в программе отдельно этой функции нет, но она фактически реализована в decrease_key. Только нужно, опять же, проходится не по всей очереди, а только по отцам. В цикле надо делать не i--, а i = (i+1)/2-1.

    И ошибка как раз в decrease_key, У вас цикл по i, а внутри все время используется k.
    Ответ написан
    5 комментариев
  • Как создать радиус в любое расстояние м/км расположенного вокруг координаты и получить список этих координат?

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

    Вам придется или скопировать код по ссылке и переписать на питоне, или использовать этот пакет.

    Дальше, вам надо начиться отступать от заданной точки на север или восток на 1 метр. Это можно или медетировать над формулой выше а можно просто запустить бинарный поиск, который будет искать изменение широты или долготы. Пусть d - сколько вам надо отложить в метрах, R - радиус земли в метрах. Тогда ищите изменение на отрезке (0, 4*r*180/(2*Pi*R)). Пробуйте откладывать текущее значение по широте или долготе, считайте расстояние по формуле и, если оно больше d, заменяйте верхнюю границу отрезка на середину, иначе заменяйте нижнюю границу. Остановите бин.поиск, когда отрезок станет достаточно мелким (например <1e-10).

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

    Сначала сгенерируйте точки на одной вертикале с начальной. Для этого повторно откладывайте 1м на север и юг от нее, пока расстояние до (x0,y0) не превысит ваш радиус. Бинпоиск достаточно запустить ровно один раз в самом начале и дальше именно это приращение откладывайте вверх и вниз.

    Потом аналогичным образом откладывайте от каждой сгенеренной точки новые точки на запад и восток. Сначала для каждой точки бинпоиском найдите нужное приращение по долготе. Потом откладывайте его влево и вправо, пока не выйдите за радиус от (x0,y0).
    Ответ написан
  • Как рассчитать "похожесть" двух словарей?

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

    В вашем примере это будет (1-2)^2+(2-2)^2+(3-0)^2+(1-0)^2 = 11.
    Чем меньше это число, тем похожее словари. Можно ее еще как-то нормировать, поделив на, допустим количество уникальных ключей в обоих словарях. Или на количество всевозможных слов.

    Если ваш язык/структура позволяет пройтись по словарю в лексикографическом порядке, то можно подсчитать такую меру за линейное время выполняя что-то вроде слияния сортированных списков. Изначально 2 указателя на минимальные элементы (по словарю) в каждом словаре. Если два элемента с одинаковым ключем, то считайте разность двух весов и двигайте оба указателья. Иначе считайте разность веса с минимальным ключем и 0 и двигайте только этот указатель. Случай, когда один из словарей уже пуст совпадает со вторым случаем.

    В питоне позволяет обходить ключи по порядку OrderedDict.
    Ответ написан
    Комментировать
  • Как побороть переполнение?

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

    У вас ошибка вот тут:
    long long div_up(int x, int y)

    Типа параметров - int. Вы когда в эту функцию передаете long long сумму - происходит переполнение.

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

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

    Изначально у всех загрузка на 0/k[i] - поэтому выбираем любую случайно или первую по порядку.
    Допустим выбрали группу a. Поставили одного сотрудника. У этой группы загрузка стала 1/k[0]. Это больше 0/k[i] для всех остальных групп, поэтому следующим сотрудником вы поставите кого-то другого.

    Можно делать тупо - двумя циклами (внешний по общему количеству сотрудников, внутренний по всем группам).
    Можно ускорить процесс, если использовать приоритетную очередь на минимум. Изначально кладете в очередь все группы с приоритетами 0. Потом достаете оттуда минимум, сотрудника этой группы ставите на заказ и добавляете в очередь эту группу назад с приоритетом +1/k[i]. Можно не класть в очередь группы, в которых не осталось незадействованных сотрудников. И тогда остановка - когда очередь пуста. Можно просто останавливаться когда у минимальной в очереди группы приоритет (k[i]/k[i]).
    Ответ написан
    Комментировать
  • K-ая порядковая статистика. В чем проблема?

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

    Представьте, что у вас массив из 2 элементов и первый - больше второго (a[0] = pivot > a[1]).

    До цикла l = 0, h = 2, i = 0. Потом в первом while вы делаете i++. Потом сравниваете a[i] с pivot в первом while. a[1] < pivot по предположению, поэтому вы делаете i = 2. Все - вы уже вышли за границу массива.

    Перепишите со стандартной схемой разбиения Хоара или Ломуто.

    Или придется всякие условия i < j везде дописывать, но я не уверен.
    Ответ написан
    2 комментария
  • Самая быстрая реализация алгоритма Дейкстры на javascript?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Попробуйте переписать с массивами фиксированной длины. Во всех реализациях по вашим ссылкам вершины нумеруются строками и куча массивов типа distance и visited на самом деле являются словарями, или как это в js называется. Это работает сильно медленнее тупого массива, пронумерованного от 0 до n.

    Вам понадобится один словарь для перенумерации вершин в числа. Потом преобразуйте гарф на массив массивов, вместо этого сложного объекта.

    И уже на нем гоняйте дейкстру. Должно по карйней мере в пару раз ускорится. А то и во все 10.
    Ответ написан
  • Какой алгоритм используется в пакетных менеджерах?

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

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

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

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Вроде все правильно. Какие ограничения? Может быть переполнение, ведь максимальный ответ n(n-1)/2. Для переполнения int достаточно 65536 чисел в массиве.
    Ответ написан
    4 комментария
  • Как разделить "веса" на кластеры КОРРЕКТНО?

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

    Варианты метрик:

    - Для каждого кластера считается наибольшее расстояние между двумя элементами, и это суммируется по всем кластерам. Можно суммировать квадраты этих расстояний, тогда будут наказываться кластеризации с очень большими кластерами.
    - отношение максимального расстояния между соседними точками в любом кластере и минимального расстояния между кластерами.
    - Это может быть и качественная метрика. Любая кластеризация, где расстояние между соседними точками в кластере меньше расстояния между кластерами считается хорошей. Это частный случай предыдущей метрики, но вам достаточно искать не минимум, а любое значение <1.

    Некоторые метрики имеют смысл только при фиксированном количестве кластеров, как первая.

    Разные метрики дают разные кластеризации и все они в каком-то смысле хорошие. Что именно подходит вам в вашей задаче - можете судить только вы эмпирически.

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

    Многие метрики, если они аддитивны как первая, можно считать динамическим программированием: f(i,k) - значение метрики если мы разбили первые i точек на k кластеров.

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

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

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

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

    Можно свести ее к integer linear programming и решать какой-то из множества существующих библиотек/солверов. Если числа маленькие, то можно или полный перебор или какую-то динамику, типа решения задачи о рюкзаке сделать (тут надо будет набранные суммы во всех приборах взять в параметры).

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вы что-то напутали.
    1.000001**2**19 тоже возвращает 1.689255227180379. Только что в консоли проверил.

    > costumePow(1.000001, 19)
    1.689255227180379
    > 1.000001**2**19
    1.689255227180379
    Ответ написан
    6 комментариев
  • Как перебрать все возможные комбинации символов?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Готовой функции нет.

    Нужно или писать рекурсивную функцию, или итеративно дописывать ко всем элементам массива по одному элементу из сделеющего множества. Просто переведите этот код на с++.

    Рекурсивная функция вроде как должна быть более дружественная к аллокациям и по этому - быстрее.
    Ответ написан
    Комментировать