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

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

    Алгоритмически тут такое решение: Постройте граф из n вершин, где есть ребро между всеми парами вершин, которые можно менять местами. Подсчитайте размеры компонент связности и перемножте их факториалы. Так, если компонента всего одна, то ответ будет n! (все возможные перестановки из n чисел можно получить так).

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

    Если нужно именно математическая формула от P, то тут все сложно, осбоенно, когда числа P достаточно большие. Например, M=6, P1=3 P2=4. Из вершин 3 и 4 вообще нет ребер, а ребро P2 есть только между крайними вершинам. Подобным образом можно создать весьма сложные структуры.

    Однако, если все P меньше равны половины N, то есть простой ответ. В графе будет GCD(P1+1,...,PM+1) компонент связности - все вершины, дающие один и тот же остаток от деления на наибольший общий делитель P+1. И ответ будет что-то вроде (floor(N/G)!)^G*(floor(N/G)+1)^(N%G) (где G - этот самый наибольший общий делитель всех P, увеличенных на 1).

    Это потому, что при достаточно маленьких P, всегда можно отложить их в одну или другую сторону и так в итоге сделать шаг длинной G.

    Доказательство примерно такое: Рассмотрим первую половину вершин. Из них можно отложить вправо самое большое P. А потом влево любое из оставшихся P. Так мы фактически отложили от всех этих вершин любую разность двух P вправо. Если сначала отложить вправо меньшее P, а потом максимальное P назад, то мы отложили ту же разность влево. Из соображений симметричности получается, что мы можем отложить любую разность P от всех вершин в любую сторону. Все разности также меньше половины N. Продолжая этот процесс, как в алгоритме эвклида, мы получим, что можно от каждой вершины отложить G, а значит все вершины с одинаковым остатоком от деления на G принадлежат одной и той же компоненте связности.

    Если какие-то P больше половины (аккуратно сами посмотрите, там +-1 где-то возможно нужно), то я не знаю формулу. Остается только писать DFS на графе.
    Ответ написан
    Комментировать
  • Как решить Ханойскую башню с 8 стержнями?

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

    В итоге пишите следующию функцию: переместить n дисков с i-ого шпинделя на j-ый при возможно запрещенных шпинделях из заданной маски. Там перебираете, куда класть сколько-то верхних дисков и на какой шпиндель. рекурсивно считаете, сколько это займет шагов, плюс сколько шагов чтобы переместить оставшиеся диски в конец, плюс, сколько переместить верхние диски с временного шпинделя в конец. Если остался один диск, то над аккуратно смотреть, а можно ли его вообще переместить с заданными запрещенными шпинделями (иначе вренуть +бесконечность, или что-то очень большое).

    Чтобы это работало быстро, надо делать мемоизацию и не считать функцию с одним и тем же набором параметров несколько раз.

    Интересно, что ответ растет гораздо медленее, чем для 3-х шпинделей:
    6
    13
    22
    36
    55
    82
    117
    163
    219
    289
    375
    484
    623
    800
    1024
    1300
    1651
    2090
    2643
    3333
    Ответ написан
    1 комментарий
  • Как предсказать следующее числовое значение по предыдущим?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Есть целая наука об этом. "Временные ряды", вроде, называется. Там есть куча всяких моделей, типа ARIMA.
    Ответ написан
    4 комментария
  • Операции AND, OR, XOR Как проверить биты?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    X AND (2^1+2^2) ИЛИ X AND 230


    Слева же правильно написано, но вы как-то из 2+4 получили 230.

    Х AND (255–2^2–2^7) ИЛИ Х AND 221

    Тоже самое. Правильную операцию сделали, но както из 255-4-128 получили 221, хотя должно быть 123.
    И вместо X пишите A или B в заданиях. Дальше такие же ошибки.
    Ответ написан
  • Как разбить данный массив?

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

    Потом пройдитесь по массиву, поддерживая счетчик открытых интервалов. Встретили "from" - увеличили счетчик. встретили "to" - уменьшили.

    Если изменили счетчик с 0 на 1, то в текущей дате начало отрезка в ответе. Если изменили с 1 на 0, то тут конец.

    Наверно, надо будет еще отрезки разбить по месяцам потом.

    Еще надо разобраться с тем, когда даты совпадают. Если в одну и ту же дату есть to и from - это же должно считаться одним отрезком? Тогда при сортировке ставьте "from" перед "to" на одну и ту же дату (В сравнении при равенстве дат сравнивайте еще и тип события).
    Ответ написан
    Комментировать
  • Как выполнить размещение k элементов из n?

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

    Работа с символами ничем не отличается от работы с числами. Во всех языках символы можно сравнивать по их кодам, и в алгоритмах кроме сравнения ничего нет.

    Или можно сгенерировать размещения из {1,2,3,...N} а потом использовать эти числа в размещениях, как индексы символов. Надергайте из заданной строки символы по этим позициям и получите размещение из символов, а не чисел.
    Ответ написан
    Комментировать
  • Как он это "заметил"?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Ну, это известная формула - количество пар среди n объектов. Для самого левого подходит n-1 правых концов. Для второго - их будет n-2. Для последнего - 0. Сумма (n-1)+(n-2)+...+1+0 = (n-1)n/2. Можно через сумму арифметической прогрессии получить.
    Ответ написан
    1 комментарий
  • Почему так долго работает сортировка слиянием?

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

    mergeSort(a, temp, 0, mid);


    Надо сортировать часть с lo по mid. А не с 0.
    Ответ написан
    Комментировать
  • Реализицая обратной польской нотации, как быть с унарным минусом?

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

    Соответственно в парсере, если видите унарный минус перед скобками, то переводите скобки в нотацию, а потом дописываете ±.
    Ответ написан
    3 комментария
  • Как решить олимпиадную задачу python?

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

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

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

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

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

    Вот набросок кода на C++, думайте, что это псевдокод.

    vector<int> ConvexHull(vector<double> x, vector<double> y) {
      int first = ...; // Тут поиск самой нижней-левой точки.
      const int n = x.size();
      int cur = first;
      vector<int> ans{first};
      // Текущий вектор стороны может идти вправо от самой нижней точки.
      double side_x = 1;
      double side_y = 0;
      while (true) { 
        int bsti = -1;
        // Вектор на лучшую точку.
        double bst_x = 0;
        double bst_y = 0;
        // Расстояние до нее.
        double bst_dist = 0;
        for (i = 0; i < n; ++i) {
          if (i == cur) continue;
          // Вектор на текущую точку.
          double cur_x = x[i] - x[cur];
          double cur_y = y[i] - y[cur];
          // Отсекаем точки назад вдоль оболочки на той же стороне.
          // Можно заменить пометкой в bool in_hull[],
          // которая ставится при добавлении точки в ответ (ans).
          if (side_x*cur_y-side_y*cur_x == 0 && side_x*cur_x+side_y*cur_y < 0) continue;
          double vec = bst_x*cur_y-bst_y*cur_x;
          double dist = cur_x*cur_x+cur_y*cur_y;
          // Берем точку, если она правее текущего кандидата. Или на той же прямой, но ближе.
          if (bsti == -1 || vec < 0 || (vec == 0 && dist < bst_dist)) {
            bsti = i;
            bst_dist = dist;
            bst_x = cur_x;
            bst_y = cur_y;
          } 
        }
        // Сделали полный оборот.
        if (bsti == first) break;
        side_x = bst_x;
        side_y = bst_y;
        and.push_back(bsti);
        cur = bsti;
      }
      return ans;
    }
    Ответ написан
    Комментировать
  • Как возвести в степень значение biginteger на biginteger?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Отличное свойство модуля в том, что можно при вычислениях брать модуль на каждом этапе (если нет делений - в этом случае надо искать обратное по модулю). Поэтому исходное выражение можно переписать так: (g^m mod n^2) * (r^n mod n^2) mod n^2

    И код вычисления этого такой:
    nn = n.multiply(n);
    res = g.modPow(m, nn).multiply(r.modPow(n, nn)).mod(nn);
    Ответ написан
  • Что не так с поиском математического ожидания?

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

    Только после исправления ваше решение, скорее всего, не пройдет по времени. Потому что K до 10^16 в задаче и вы итерируетесь до него.

    Преобразуйте вашу формулу для difference - вынесите за скобки chess[i].number. Получите что-то вроде abs(chess[i].number*A + B).

    И найти минимум этой линейной функции можно в одно действие - это -B/A. Но это если искомое число может быть дробным. Для целых чисел вам надо посмотреть 2 значения - округленное дробное вниз и вверх. Можно их получить проводя все вычисления только в целых числах. А дальше надо выбрать одну из двух меньших разностей, при условии, что chess[i].number там не равен K.
    Ответ написан
  • Есть ли уже существующая структура данных для данной задачи?

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

    Постройте двумерный массив, который будет говорить, можно ли собрать данную сумму первыми k объектами.

    Используя его можно относительно быстро восстанавливать все множества с заданной суммой а заодно посмотреть, какие суммы возможно набрать.
    Ответ написан
    1 комментарий
  • Как найти нижнюю границу сложности алгоритма?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Тут на глаз можно. Очевидно же, что T(n) > n. Поэтому можно взять n как нижнюю границу.
    Ответ написан
    Комментировать
  • Что не так с решением задачи?

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

    Надо порисовать на бумажке и убедится, что из строки S вы можете получить только строки вида S(k1)S(k2)S(k3)...S(kn), Где S(x) - x-ый префикс строки S, т.е. первые x символов.
    При этом k1 >= k2, ... k1>= kn.

    При чем все строки такого вида можно получить.

    Доказывается по индукции по количеству операций. Изначально строка именно такого вида (k1=длина строки). После каждой операции, эта строка сколько-то раз копируется и как-то обрезается по длине. Поэтому любая полученная строка будет такого вида. И каждую такую строку можно построить: применяйте операции с k равными k1, k1+k2, k1+k2+k3... Так вы за n операций соберете все префиксы по одному.

    Теперь, раз вам во входном файле задана подстрока результата, вам надо проверить, что эта строка состоит из подстроки S, за которой идут префиксы строки S.

    Так, пример из условия bcabc состоит из подстроки bc и одного префикса abc.

    В задаче не очень большие ограничения, поэтому можно в тупую для каждой подстроки i..j результирующей строки определить, является ли она суффиксом первой строки. Также для каждой подстроки первой строки можно определить, является ли она префиксом второй строки.

    Потом нужно построить граф: из позиции 0 сделайте ребра во все позиции, где заканчивается какая-то подстрока первой строки в начале второй. Из позиции i>0 сделайте ребра во все позиции j такие, что подстрока i...j префикс первой строки.

    Потом, если в этом графе есть путь из вершины 0 в вершину с номером длины второй строки - YES. Иначе - NO.

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

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

    Если вы место обращения к массиву будете делать currentByte & (1 << (7-j)), то должно работать почти также быстро.

    P.s. и вообще у вас какой-то странный выбор порядка битов. Обычно сдвигают вправо, к младшему биту. Тогда вместо маски будет тупо 0x1.
    Ответ написан
  • Может есть алгоритм равномерного распределения на основе хэша SHA256?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Если вам надо, чтобы после увеличения n, хеши выдавали те же позиции - то это невозможно сделать равномерно без запоминания. Потому что пусть n=10 изначально - тогда ЛЮБОЙ хеш должен выдать значение меньше 10. А потом при увеличении n любой хеш, все-равно должен выдать 0-9. Пусть даже n станет 100000 - нельзя использовать добавленные позиции, кроме 10 первых.

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

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

    Подсказка: тут отлично зайдет цикл do ... while. Одна переменная должна указывать, кто из людей сейчас активный. Вы должны дописать к ответу его букву и перейти к другому человеку взяв соответствующий элемент из массива A. Цикл должен работать, пока вы не вернетесь к первому человеку. Само решение будет буквально 3 строки, если не считать скобки.
    Ответ написан
    Комментировать
  • Как усовершенствовать алгоритм для уравнения Диофанта?

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

    Там по вашей ссылке в задаче есть подсказка: уравнение можно переписать в виде (x-2y)(x+2y)=n

    Отсюда получается решение: Перебирайте все делители n, не превосходащие корень. Пусть делитель d, тогда второй множитель будет n/d.

    Осталось решить систему линейных уравнений:

    x-2y=d
    x+2y=n/d


    Решается в уме - x=(d+n/d)/2, y=(n/d-d)/4

    Отслось только учесть диофантовость уравнения - ответ должен быть неотрицательные целые числа. Значит, надо чтобы оба делителя d, n/d давали одинаковый остаток от деления на 4 и 2. Ну и d<=n/d, но это учтется перебором делителя до корня.

    Вот и все решение - циклом переберите делитель до корня, убедитесь, что он и оставшийся множитель дают один и тот же остаток по модулю 4, вычислите x и y и выведите их.
    Ответ написан
    Комментировать