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

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

    Если вы видите, что у вас однобуквенных слов меньше, чем букв в алфавите - то можно выдать однобуквенное слово. Иначе, если двух-буквенных слов меньше |alphabet|^2, то можно выдать двубуквенное слово. И так далее. Вот нашли вы минимальную длину.

    Чтобы найти и само слово, заведите битмап размером со сколько у вас слов. Потом пройдитесь по словам и если слово нужной длины, поставьте 1 в битмапе с номером равным строке, если ее интерпретировать как число в |alphabet|-ой системе счисления (самый младший символ равен цифре 0). Если число переваливает за размер битмапы, то можно текущую строку проигнорировать. Потом найдите любой 0 бит в битмапе. Его номер переведите назад в |alphabet| систему счисления.

    Пример. Алфавит - {a,b}. Слова: "a", "b", "aa", "ab", "bb".

    Мы видим 2 однобуквенных слова. Никак. 3 двухбуквенных, но 3 < 2^2 - значит можно выдать двубуквенную строку.

    У нас 2 символа в алфавите, интерпретируем a=0, b=1. "aa" = 0, "ab" = 1, "bb" =3. В битмапе останется пустым индекс 2. Это 10 в двоичной системе или "ba" в строках - это и есть ответ. Да еще и лексикографически минимальный.

    Если слова могут повторятся, то можно чуть изменить решение: точно также перенумеруйте все возможные слова в вашем алфавите. Сначала будут однобуквенные, потом двубуквенные и т.д. При переводе строки в индекс - также переводите из |alphabet|-ичной системы счисления. Но для слова из l символов прибавляйте смещение |alphabet|^(l-1)+|alphabet|^(l-2)+... |alphabet|^1. Точно так же, если в процессе вычисления видно, что индекс не поместится в битмапу (больше количества строк), то можно забить на эту строку. Поэтому же можно сразу пропускать слишком длинные слова.

    Опять же, помечайте бит в битмапе. Потом найдите первый нулевой бит. И назад преобразуйте в строку - сначала найдите смещение (суммируйте степени |alphabet|, пока они не перевалят за текущий индекс). Так вы поймете, сколько символов у вас в строке. Вычтите смещение и переводите индекс в |alphabet|-ичную систему счисления.

    Этот вариант в среднем может работать чуть дольше, но все так же очень быстро.

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

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Куча ошибок. Вы читаете запросы в массив B и больше нигде не используете. Конструкция A{m<i] вообще удивительна. Массив надо отсортировать.
    Ответ написан
  • Как перемешать массив в псевдослучайной последовательности?

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

    Или можно сортировать хеши, полученные из id тега и id статьи. Например, можно подсчитать tag_id*article_id % n.
    Ответ написан
  • Почему не работает такое решение?

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

    Чтобы через DFS найти отрицательный цикл - придется перебирать ВСЕ циклы. Для этого надо не помечать вершины как visited. Правда, работать будет очень медленно и почти гарантированно не пройдет по time limit, ибо циклов в гарфе может быть экспоненциально много.

    Edit, вот вам пример. В графе только один отрицательный цикл 1->2->3->1. Но есть еще ребро 1->3. Если вы начнете из 1-ой вершины, потом возьмете ребро 1->3, то дальше вы из вершины 3 никуда не сможете пойти, уберете ее из стека и пометите как visited. Далее вы рассмотрите ребро 1->2 а потом из вершины 2 ребро 2->3 вы пропустите, потому что вершина 3 уже visited.

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

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

    Сортировать можно разными способами. Например, читать кусками сколько помещается в память, отсортировать как угодно, записать на диск. Потом получившиеся отсортированные куски можно объединять, как в сортировке слиянием.

    Еще можно использовать radix sort.

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

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

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

    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)
    Ответ написан
    Комментировать