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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Идея этого усечения в том, что если вы ищите gcd(a, b), то можно также искать gcd(a,a-b). Вот эта замена b' = a-b у вас в коде и происходит.

    Вы там поддерживаете инвариант x_i*a+y_i*b = r_i

    Сответсвтенно, вам надо пересчитать x_cur и у_cur вместе с изменением r_cur.

    У вас есть x_prev*a+y_prev*b = r_prev и x_cur*a+y_cur*b = r_cur. Вам надо составить линейную комбинацию r_prev - r_cur. Ну вычтите одно уравнение из другого и все.

    Соответственно вам надо сделать вот это при применении вашего усечения:
    x_cur = x_prev - x_cur
    y_cur = y_prev - y_cur
    Ответ написан
  • Как свести задачу к минимальному разрезу?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вам нужен 2-дольный граф. Левая доля соответствует строкам, правая - столбцам. Из истока ребора в левую долю (ценой ri), из правой доли - в сток (ценой ci). Ребра между долями ценой aij.

    Любой разрез тут соответствует покрытию всех вершин. Вы должны разрезать ребро между ij или si или jt что чоответствует покрытию клетки, строки иои столбца.
    Ответ написан
    1 комментарий
  • Где ошибка в доказательстве?

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

    Правильное доказательство весьма простое и без индукции. Общее время будет
    Max(sum{i-начальнику}(bi), max{i - подчиненным}(ai))
    .
    Отсюда видно, что если вы какую-то задачу дали подчиненному, то все задачи с меньшим A нет смысла давать начальнику. Ведь второе слагаемое-максимум от этого не изменится, но первое увеличится. Можно "бесплатно" выполнить эти задачи подчиненными. Поэтому в оптимальном ответе вы даете какое-то множетсво минимальных A подчиненым, а остальные начальнику. Иначе можно какие-то задачи перекинуть с начальника на подчиненного и уменьшить первое слагаемое не меняя второе, уменьшив таким образом ответ, т.е. в этом случае решение точно не оптимальное.
    Чтобы все варианты возможных оптимумов перебрать надо отсортировать задачи по возрастанию A. Это код и делает. поддерживается сумма B у всех оставшихся задач, а от текущей берется A - который и будет максимумом для всех задач с меньшим A.
    Ответ написан
    Комментировать
  • Как решать задачу?

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

    Рассмотрим теперь один уровень, который описан 4 числами a,b,c,d и нам надо оставить как можно больше шаров белого цвета (их d). За один ход мы можем приравнять к 0 одно из 4 чисел и вычесть по 1 из отсавшихся ненулвевых. Ясно, что нет смысла занулять d. Т.о. за 3 хода мы можем получть 0,0,0,max(0,d-3). Но, например, если у нас было 2 2 2 3, то занулив a и b мы уменьшениями на 1 зануляем и c. Т.е. для маленьких чисел имеет смысл подумать в каком порядке их занулять. Но мне лень даже думать как именно - ведь их всего 3 числа - можно тупо перебрать все 6 перестанвок и выбрать ту, в которой за наименьшее количество ходов мы их все занулим.
    Ответ написан
    6 комментариев
  • Почему в Python последовательность append-ов суммируется в O(n), а не в O(n logn)?

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


    Потому что эти распределения разного размера. Первое - совсем маленькое. Второе чуть больше и т.д. Чтобы суммарно было O(n Log n), каждое из них должно быть пропорционально n.

    1+4+10+⋯≤O(n)


    Вот это и есть ответ. Первые слагаемые маленькие. Получается геометрическая прогрессия, которая дает линейную сумму от n.
    Ответ написан
    Комментировать
  • Как корректно распределить сумму внутри элементов массива?

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

    Далее алгоритм (на псевдокоде):
    total_price - изначальная цена. goods[i].price - цена товара, goods[i].amount - количество товара, discount - сколько скидки.
    discount_left = discount
    for i in 1..n {
      cur_discount = discount * goods[i].price / total_price # целочисленное деление нацело с округлением вниз.
      goods[i].price -= cur_discount
      discount_left -= cur_discount*goods[i].amount
    }
    for i in 1..n {
      if discount_left == 0: break
      if goods[i].amount <= discount_left {
        goods[i].price -= 1;
        discount_left -= goods[i].amount
      }  else {
       // разбиваем категорию на 2 штуки, в одной discount_left товаров, в другой остальное.
       new_good = Good{.price = goods[i].price-1, .amount = discount_left}
       goods[i].amount -= discount_left
       discount_left = 0
       goods.append(new_good)
       break
      }
    }


    Как это работает: если бы мы могли идеально делить копейки с любой точностью, то каждый товар получил бы скидку price*discount/total_price. Одинаковый процент скидки везде. но у нас-то целые копейки, поэтому если мы эти скидки округлим вниз, то мы сколько-то копеек недоскинем. Но для каждой штуки товара мы потеряли меньше одной копейки, а значит суммарно - меньше общего количества товаров. Значит можно из каких-то товаров вычесть 1 и все сойдется. Вот и вычитаем из первых discount_left товаров. Если категория целиком покрыта - просто уменьшаем у нее цену. Если нет, то разбиваем на 2 куска и у одного цену уменьшаем.

    Тут могут быть проблемы, если скидка очень большая и есть товары очень маленькой стоимости. Скажем, товар стоимостью 5 копеек и кидка в 85% уже между 0 и 1. Округленная вниз она будет 4, но вот если вы из этого товара потом еще и 1 вычтите, то может так получится, что вы снизите цену до 0. Допустимо ли это? Чтобы этого избежать надо товары отсортировать по убыванию цены. Но все равно может не помочь, тогда надо тогда такие товары ценой в 1 копейку исключить из рассмотрения и запустить цикл вычитания 1 опять, если после цикла discount_left не 0.

    Второй момент, если вам очень хочется избегать разбиения категорий на части, то можно попробовать реализовать динамическое программирование для решения задачи размена монет, и таким образом найти какие категории надо взять целиком, чтобы набрать discount_left. Но вряд ли вам это так уж надо, ибо предвижу весьма частый случай, когда разбивать все-равно придется. Да и разбиение тут максимум одно происходит.
    Ответ написан
    Комментировать
  • Как доказать что мы действительно не пропустим такую пару i,j которая дает правильный ответ в методе двух указателей?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В такой реализации это плохо видно, но можно переписать основной цикл так:
    j = Len(B)-1
    for i in range(len(A)):
      while j >= 0 and A[i] + B[j] > c:
        --j;
      if A[i] + B[j] == c:
        found = True
        break


    В этом случае после цикла while поддерживается инвариант, что a[i]+b[j] <= c и это максимальное такое j.
    Ведь, если этот инвариант поддерживался для пердыдущей итерации, то у нас было a[i-1] +b[j] <=c и a[i-1]+b[j+1]>c. Отсюда получается, a[i]+b[j+1] >= a[i-1]+b[j+1] > c. Т.е. если мы уменьшим j в цикле while инвариант останется действовать - это будет самое большое j, т.ч. a[i]+b[j] <= c.

    А этот инвариант гарантирует, что когда в цикле по i переберется значение из ответа, j гарантированно будет указываать на j из ответа. Потому что, если существуют i', j', т.ч. a[i']+b[j'] = c, то можно увеличить j' пока b там не меняется, т.ч. b[j']>b[j'], отсюда получается a[i']+b[j'] <=c и a[i']+b[j+1] > c - а это и есть наш инвариант. Т.е. на итерации i=i' найдется именно j=j'.
    Ответ написан
    3 комментария
  • Как найти ближайщую точку из списка географических координат?

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

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

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

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

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

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

    Обычно, сначала реализуют дискретную случайную равномерно распределенную величину. Гуглите алгоритмы генерации псевдослучайных чисел, если вам нельзя даже какой-нибудь rand() использовать.

    Далее получают равномерно распределенную случайную величину на отрезке [0,1]. Для этого генерируют случайное число от 0 до MAX_RAND и делят на MAX_RAND.

    Произвольную же случайную величину можно получить, воспользовавшись свойством функции распределения: Если подставить равномерно распределенную величину в обратную функцию распределения, то получится случайная величина с заданным распределением.

    Пусть x искомая случайная величина Fx(t) = P(x < t). u -равномерно распределенная случайная величина. Тогда x = Fx^(-1)(u).

    Например, для экспоненциальной случайной величины Fx(t) = 1-e^(-lt). Обратная функция будет Fx^(-1)(y) = -ln(1- y)/l. Значит считаете ваше случайное число, делите на MAX_RAND, подставляете в формулу -ln(1-y)/l. Или можно упрастить и брать просто -(ln y)/l, потому что равномерная случайная величина от 0 до 1 симметрична.

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

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

    Надо поменять условия в циклах for и while.
    Ответ написан
  • Как составить наиболее эффективный алгоритм групповой рассылки сообщений по каналам WebSocket?

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

    Единственное, что вы можете соптимизировать - это нахождение списка пользователей.

    Вам надо как-то побыстрее определить, какие пользователи сейчас онлайн из данного канала. Можно или поддерживать эту информацию в памяти (это будет map из channel_id в set user_id). Когда пользователь выходит в онлайн, надо добавить его в структуру данных для всех каналов, на которые он подписан. Когда пользователь отваливается - удаляйте его из памяти и чистите структуру (надо удалить ключ из map, если там значение осталось пустым).

    Еще вариант: сделать это прямо в базе данных. Для каждого пользователя поддерживайте флаг - онлайн ли он. И в базе данных спрашивайте список всех онлайн пользователей, которые подписаны на нужный канал. Можно даже ваш map user_id->socket_channel_id в базу запихать.
    Ответ написан
    Комментировать
  • Бинарный поиск. Правильно ли работает?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    if i == middle - 1 or i >= 5 and len(list) <= 4:
                return -1


    Вот эта часть вызвает вопросы. Отладочный код остался?

    Проблема в вашем коде, что он виснет, если числа в массиве вообще нет. Условие должно быть не про равенство, а про то, что у вас отрезок пуст (или длины 1) - надо сравнивать first и last.

    Обычно middle не поддерживают между итерациями цикла, а просто считают внутри. Между итерациями переходят только first и last.

    А так, да, бинпоиск - это весьма простой алгоритм.
    Ответ написан
    1 комментарий
  • Дейкстра почти за линейное время (для неориентированного графа)?

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

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

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

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

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

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

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

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

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

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Потому что это 2 разных графика, 2 разных примера. Могли бы на втором обозвать вместо f и g f` и g`. Но авторам, наверно, было лень.
    Ответ написан
    1 комментарий
  • Как проходить список и одновременно удалять элементы, в том числе впереди курсора?

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

    Еще можноиспользовать обычный list, только вы итерируйтесь циклом while и используйте индексы:
    i = 0
    while i < len(arr):
      x = arr[i]
      j = i+1
      while j < len(arr):
        if ShouldDelete(arr[j], arr[i]):
          del a[j]


    Это будет в n раз медленнее, к сожалению. Можно наверно слайсами сделать быстрее:

    arr[i+1:] = y for y in arr[i+1:] if not ShouldDelete(x, arr[i])


    Я не питонист и возможно ошибку допустил. И мне этот код не кажктся очень понятным, но возможно это истиный путь для питона.

    А так, в общем случае, идет двойная итерация с пометками элементов на удаление.

    Какого-то названия этот прием не имеет, ибо это костыль для конкретных реализаций контейнеров.
    Ответ написан
    Комментировать