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

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

    Самое быстрое - это обработать 2 случая, элемент изменил свое значение вниз или вверх. Потом циклом или влево или вправо надо swap-ать его со следующим, пока они стоят не в том порядке. Можно чуть соптимизировать и выкинуть лишние операции из каждого свап-а:
    // a[moved] увеличился.
    tmp = a[moved];
    for (i = moved+1; i < a.size() && a[i] < tmp; ++i) {
      a[i-1] = a[i];
    }
    a[i-1] = tmp;
    Ответ написан
  • Как найти пустую ячейку массива с наименьшим индексом?

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

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

    При удалении просто добавляйте пустую ячейку в начало списка (эта ячейка указывает на текущую голову списка; голова теперь указывает на эту ячейку).

    УПД:

    Это все выше, если забить на требование заполнения самой левой пустой ячейки. Зато все операции (удаление, поиск пустой ячейки) выполняются за константу и не надо константную дополнительную память.

    Если обязательно заполнять самую левую ячейку - то надо писать или приоритетную очередь (через heap) или дерево отрезков (или дерево Фенвика). Тогда операции удаления и поискка будут работать за логарифм и потребуется линейная дополнительная память.
    Ответ написан
    5 комментариев
  • Не могу верно решить задачу из ЕГЭ по инфе. Почему ответ неверный?

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

    Надо делать динамическое программирование:

    F(n, k) - максимальная сумма, которую можно получить, взяв какие-то числа из первых n пар и при этом получить k нечетных чисел.

    F(0,0) = 0

    F(0,*) = -infinity

    F(n,k) = max_i=0..1 a[n-1][i]+F(n-1,k-a[n-1][i]%2).

    Ответ: max_i=0..n: F(n,i) (т.ч. F(n,i) - той же четно для i<=n//2 и нечетно для i > n//2)
    Ответ написан
    Комментировать
  • Как сравниваются перцептивные хэши?

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

    Например, можно проиндексировать все последовательности из n символов подряд в каждом хеше. Потом поискать в этом индексе все последовательности из n символов в образце. Потом хеши из получившегося списка уже проверить на расстояние хемминга. Если брать n < L/k, где L - длина хеша, а k -допустимое количество ошибок, то все нужные хеши попадут в список. Чем больше n, тем меньше лишнего будет в списке.

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

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

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

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

    Для проверки принадлежности точки отрезку можно воспользоваться свойствами векторов. Точка C лежит на отрезке AB, если
    1) векторное произведение <AB,AC> = 0
    2) Скалярное произведение (AB,AC) > 0
    3) Скалярное произведение (AB,AC) < |AB|^2 (длина отрезка в квадрате).

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

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Как написал Илья, можно растеризовать картинку и подсчитать незакрашенные пиксели. Это простой в реализации метод, но, если нужна высокая точность, он будет очень медленным и требовательным к памяти (экспоненциальное время и память от количества нужных знаков). Однако, есть алгоритм, который позволяет получать площадь в символьном виде (типа 3/2+5113/11*sqrt(31)). Ну, или любое нужное вам количество знаков без огромного потребления памяти или вычислений (метод полиномиален от количества вычисляемых знаков).

    Это некоторое обобщение метода выделения областей или нахождения граней планарного графа.

    Сначала вам надо пересечь все пары окружностей и окружности с прямоугольникм, назначить всем уникальным точкам уникальные номера. Потом надо на каждой окружности составить список всех точек пересечений на ней и отсортировать его вдоль окружности (допустим, против часовой стрелки). Тут же надо добавить самую левую и самую правую точку на окружности (X+-R, Y) - это поможет считать площадь позже. также надо составить список всех точек на прямоугольнике, отсортированных также против часовой стрелки. В этот список надо включить вершины прямоугольника.

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

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

    После этой сортировки вы в каждой вершине имеете все ребра отсортированные по углу. Теперь можно для каждого исходяжего из вершины ребра найти следующее по часовой стрелке. Это то ребро, которое получится, если стоя в точке пересечения пойти не по этому ребру, а по тому, что идет чуть-чуть левее:
    вот картинка mad paint skillz
    60ae079489dbd754935211.png


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

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

    В итоге каждая замкнутая область будет обойдена против часовой стрелки. Кроме одного исключения - вся картинка будет обойдена по границе по часовой стрелке, но это можно легко проверить через векторные произведения соседних дуг (касательных) и выкинуть эту область из рассмотрения. У вас будет список дуг/отрезков прямоугольника. Теперь осталось понять, какие области не покрыты окружностями. Во-первых, если область имеет выпуклую дугу (дуга против часовой стрелки вдоль окружности), то область 100% внутри окружности. Во-вторых, надо взять любую вершину области и проверить, не лежит ли она строго внутри какой-либо окружности.

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

    Вот так и считается ответ. Все координаты можно считать в символьном виде - они будут вида a/b+sqrt(c)/d. Вектора касательных будут такого же типа. Такие числа можно перемножать, складывать, вычитать и сравнивать. Правда, после операций вы получите множество радикалов, но тут нет проблем, потому что сравнивать результаты вам уже не придется. Ну, или тут можно использовать числа с плавающей запятой и спокойно получить 12-14 знаков точности.
    Ответ написан
    Комментировать
  • Какая ошибка в решении задачи о коммивояжёре методом перебора с возвратом?

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

    Попробуйте переписать с помощью std::next_permutation для перебора всех перестановок. Просто для сравнения и проверки теста - вдруг там опечатка.

    Оно будет работать медленнее рекурсивного перебора из-за отсутствия отсечений, да и одно и то же решение с циклическим сдвигом будет получатся n раз, но для 10 городов должно быстро отработать. Но зато код будет совсем простой: один цикл, как в примере из документации перебирает все перестановки от 0 до n-1. Внутри вы циклом эти числа берете как индексы городов и суммируете расстояния между ними + расстояние между последним до начального. И запоминаете, если сумма меньше известного минимума. Если и там 168 получится - в тесте опечатка.
    Ответ написан
  • Как замостить ориентированный по осям Rectangle блоками максимального размера, обходя препятствия?

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

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

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

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

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

    Можно в тестах требовать совпадение с некоторой абсолютной или относительной погрешностью.

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

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

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Если тут произвольная длина в смысле не "вам надо считать ровно 5 символов", то можно читать в std::string. Оно само выделит достаточно памяти.

    Вот так прочтется одно слово до пробела (или конца файла):
    std::string s;
    std::cin >> s;


    Вот так прочтется строка целиком до перевода строки (или конца файла):
    std::getline(cin, s);

    Потом можно по string проходится как по массиву, от 0 до s.length()-1.
    Ответ написан
    Комментировать
  • Алгоритм Рабина-Карпа. В чем ошибка?

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

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

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

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

    Но, если так надо, то вам нужно для каждого числа сначала узнать номер старшего ненулевого бита (most significat bit - MSB), и потом сдвинуть верхнее число вправо на половину разницы в позициях старших битов.

    1 = 0000001b, msb = 0
    18 = 0010010b, msb = 4

    Разность - 4 разряда. Значит, надо сдвигать на 2 разряда вправо:
    18 >> 2= 0010010b >> 2 = 00100b = 4

    Еще пример: для 4 и 18 msb были бы 2 и 4, разность -2. Сдвинув 18 на 2/2=1 разряд вправо, вы бы получили 9.

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

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

    Для предподсчета просто в цикле задавайте msb[i] = msb[i/2] + 1;
    Ответ написан
    Комментировать
  • Как выглядит стандартное решение эллипса по центру и 3 точкам?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    TL;DR:
    S = Pi*sqrt(3 / [ (1/L1+1/L2+1/L3)^2-2*(1/L1^2+1/L2^2+1/L3^2) ])

    тут L1, L2, L3 - квадраты трех измерений.

    Вывод формул:
    Чуть-чуть аналитической геометрии и куча элементарной алгебры помогут вам вывести эти уравнения.
    Если ввести систему координат с осями по главным радиусам эллипса, то уравнение эллипса будет:
    x^2/a^2+y^2/b^2 = 1

    При этом все точки под углом alpha к большой полуоси будут на луче:
    x(t, alpha) = t*cos(alpha)
    y(t, alpha) = t*sin(alpha)


    Обозначим синус и косинус угла для первого измерения как:
    s = sin(a1), c = cos(a1)

    Пусть квадраты расстояний - L1, L2, L3.

    Если пересечь луч и эллипс, то можно составить уравнение для длины вдоль угла a1 (первое измерение), просто подставив известные x(a1, sqrt(L1)) и y(a1, sqrt(L1)).
    1/L1 = c^2/a^2+s^2/b^2

    Для остальных измерений надо прибавлять 120 градусов к углу под cos и sin. Если раскрыть cos(120+a1) = cos(120)cos(a1)-sin(120)sin(a1) и sin(120+a1) = cos(120)sin(a1)+sin(120)cos(a1), то можно составить еще 2 уравнения:

    1/L2 = (-1/2*c-sqrt(3)/2*s)^2/a^2+(sqrt(3)/2*c-1/2*s)^2/b^2
    L3 = (-1/2*c+sqrt(3)/2*s)^2/a^2+(-sqrt(3)/2*c-1/2*s)^2/b^2


    Всего с учетом тригонометрического тождества s^2+c^2=1 у нас 4 уравнения на 4 неизвестных a, b, c, s.

    Но нам не нужны все значения. Площадь эллипса Pi*a*b, а радиусы эллипса - a и b.

    Раскрыв квадраты выше и всячески складывая эти уравнения можно получить в итоге
    1/a^2+1/b^2 = 2/3*(1/L1+1/L2+1/L3)
    1/a^2*b^2 = [ (1/L1+1/L2+1/L3)^2-2*(1/L1^2+1/L2^2+1/L3^2) ]/3


    Отсюда площадь:
    S = Pi*ab = Pi*sqrt([ (L1+L2+L3)^2-2*(L1^2+L2^2+L3^2 ]/3)


    Чтобы найти радиусы эллипса, вам надо найти a и b. Выше уже даны два уравнения для суммы и произведения 1/a^2 и 1/b^2 - можно из них получить квадратное уравнение для t=1/a^2. Два его решения и будут вашими радиусами эллипса (не забудьте взять корни и обратить). Тут надо аккуратно подставить выражения в школьные формулы для квадратного уравнения.
    Ответ написан
    9 комментариев
  • Является ли эффективность данного алгоритма O(n*log(n))?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Нет, тут O(n^2). С чего вы там взяли log?
    Ответ написан
    1 комментарий
  • Как оптимизировать алгоритм подсчёта остатка на складе по системе LIFO (последний пришел первый ушел)?

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

    Обрабатывайте все записи в хронологическом порядке.

    При приходе in, вы добавляете в стек (через append у list-а) пару (цена, количество).

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

    Что-то вроде такого (сам не питонист, возможно придется переписать немного)

    def getOstatokLIFO(df):
      stack = [];
      for index, row in df.iterrows():
        if row.operation == "in":
          stack.append([row.price, row.quantity])
          continue
        left = row.quantity
        while left > 0:
          if left >= stack[-1][1]:
            left -= stack[-1][1]
            stack.pop()
          else:
            stack[-1][1] -= left
            left = 0
            
      return stack


    Но вообще, DataFrame очень медленно работет при такой последовательной обработке, поэтому я бы посоветовал вам не создавать pandas dataframe изначально, и получить ваши данные в виде списка словарей, touples или объектов. А алгоритм расчета предполагает последовательную обработку.
    Ответ написан
    Комментировать
  • Как найти количество путей в невзвешенном графе с символами в узлах?

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

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