Ответы пользователя по тегу Алгоритмы
  • Через сколько клеток проходит отрезок?

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

    Ваше решение не работет, если отрезок вертикальный или горизонтальный. Ответ должен быть 0, вы же выведите что-то другое.

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

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

    Или считайте по формуле ans = from + (to-from+0.0)*time/duration
    Ответ написан
    Комментировать
  • Как решить задачу?

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

    Проблема в том, что вы, например, пропустите число 2^23, которое входит в первые 10000.

    Вам надо поднимать границы от 22, пока сгенерированная последовательность (первые 10000 чисел) не перестанет менятся. Вы сгенерируете первые 10000 чисел точно, и какие-то дальше в последовательности (возможно с пропусками) - всего будет сильно больше 10000 чисел.

    Но скорее всего, придется поменять алгоритм. Поскольку ограничения маленькие, то тут можно делать что-то типа BFS. Вы вычисляйте числа в последовательности по порядку. И поддерживайте множество возможных следующих чисел. Каждый раз дописывайте минимальное число из кандидатов к последовательности и добавляйте к кандидатам это новое число, умноженное на 2, на 3 или на 5. Это будет квадратичный алгоритм. Можно использовать heap или какой-нибудь set, тогда будет за O(n log n).

    Еще есть линейный алгоритм. Он получается из предыдущего так: все кандидаты разбейте на 3 множества - те, которые получены домножением на 2, на 3 и на 5. Каждое такое множество в итоге будет тупо сама же последовательность, домноженная на 2, 3 или 5. Поэтому единственное, что вам надо знать для полного описания кандидатов - это какое число из последовательности было домножено на 2,3 или 5. Это три индекса.

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

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

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

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

    Эта структура будет использоватся для хранения предподсчитанных ответов. Если все ваши отрезки нарисовать на одной прямой, то она разобъется на O(n) отрезков, все точки которого будут давать один и тот же ответ при запросе. Мы эти все ответы компактно сохраним.

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

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

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

    Это решение требует O(n log n) памяти (где n - количество всех отрезков) и O(n log n) времени на предподсчет и O( log n + (ответ)) времени на обработку ответа.

    Более простое решение, где ответы считаются так же сканирующей прямой, но сохраняются просто в виде списков, а не версий персистентного дерева, может требовать O(n^2) памяти. Но будет работать быстрее, конечно.
    Ответ написан
    1 комментарий
  • Как найти первый нулевой бит в байте?

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

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

    // пусть x = 00001011b
    x = ~x;  // 11110100b
    x = x ^ (x-1);  // 11110100 xor 11110011 = 00000111
    x = ((x >> 1) & 01010101b)+(x & 01010101b);  // 00 00 01 10
    x = ((x >> 2) & 00110011b)+(x & 00110011b);  // 0000 0011
    x = ((x >> 4) & 00001111b)+(x & 00001111b);  // 00000011 = 2
    x -= 1; // индексация с 0


    Отдельно надо проверить, вдруг изначальное число было 255 - в таком случае искомого бита нет, но этот код вернет 7.

    Можно сделать для большего количества бит, надо только еще операций добавить и маски расширить. Для 32-битных чисел надо будет добавить еще 2 операции.

    Еще есть вариант с ассемблером в x86 есть операция popcnt.
    Ответ написан
    2 комментария
  • Известен ли эффективный алгоритм поиска всех элементарных циклов в неориентированном графе?

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

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

    Обычно под словом "эффективный" подразумевают полиномиальный алгоритм. Такой алгоритм тут, очевидно, не существует (потому что надо перебрать экспоненциальное количество объектов).

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

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Используйте функцию max.

    Значение в каждой ячейке будет
    max(0, (тукущее значение) - max(0, (платеж) - (сумма всех следующих ячеек)))


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

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

    Никакого переполнения быть не может, bigInteger вам не нужен. Даже long не нужен: максимальная длина пути - 10000.
    Ответ написан
    7 комментариев
  • Есть ли ошибки в реализации fft?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Зачем buffer[i] *= 1;?

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

    Если же делать с нечетными n, то эти стандартные формулы не работают.

    Еще возможно по коду разбросаны всякие off by one error и перепутанные знаки (почему в w() аргумент у синуса/косинуса берется со знаком минус?)

    Еще недочет - вы 2 раза вызываете w(), когда как можно было бы и запомнить результат первого вызова.

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

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

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

    База: F(0,0) = 0, F(0,k) = -infinity.
    Ответ: F(n, 0).

    Пересчет
    F(i,k) = max(F(i-1, ((k-a[i][0]-a[i][1])%4+4)%4)+a[i][0]+a[i][1], 
                 F(i-1, ((k-a[i][1]-a[i][2])%4+4)%4)+a[i][1]+a[i][2], 
                 F(i-1, ((k-a[i][0]-a[i][2])%4+4)%4)+a[i][0]+a[i][2])


    Только лучше вместо -infinity как-то отмечать, что позиция (i,k) недопустима (нельзя первыми i тройками набрать сумму с остатком k. И недопустимые варианты при выборе максимума выбирать ненадо.

    Еще, вместо перебора всех пар из трех элементов лучше брать сумму трех и вычитать один, который перебирается циклом.
    Ответ написан
  • Как лучше сортировать данные в которых не отсортирован только 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". А так, надо реверс инженирить сайт.
    Ответ написан