Задать вопрос
Ответы пользователя по тегу Алгоритмы
  • Удалить из ряда элементы ,как?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Например, вот так:
    def GenerateAll(source, rep):
      for comb in itertools.combinations(range(len(source)), len(source)-len(rep)):
        s = list()
        prev = 0
        for (i, j) in enumerate(comb):
          s.append(source[j]);
        yield "".join(s)


    Обратите внимание, перебираем не сочетания из удаляемых позиций, а из 22 оставляемых (кстати, у вас числа не бьются, 48+22 = 60 != 64). Тут порядок в ответе будет обратный (первая строка будет "xxxxxx...xxxx12..." а не "12..xxx..xx". Если нужен тот же порядок, то будет чуть сложнее:
    def GenerateAll(source, rep):
      for comb in itertools.combinations(range(len(source)), len(rep)):
        s = list()
        prev = 0
        last = 0
        for (i, j) in enumerate(comb):
          while (last < j):
              s.append(source[last]);
              last += 1
          last += 1
        while (last < len(source)):
              s.append(source[last]);
              last += 1
        yield "".join(s)


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

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Вообще, вот алгоритм для произвольных слов: https://e-maxx.ru/algo/longest_increasing_subseq_log

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

    Какие у вас там ограничения: длины строк и количество повторений?
    Ответ написан
  • Какие темы самые важные для олимпиадного программировании в книге Кормена «Алгоритмы. Построение и анализ»?

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

    Менее важные, но встречающиеся темы:
    Глава 30. Полиномы и быстрое преобразование Фурье
    Глава 31. Теоретико-числовые алгоритмы
    Глава 32. Поиск подстрок
    Глава 33. Вычислительная геометрия

    Но лучше прочитать, таки, всю книгу. Вправляет мозги, учит строить математические модели задач, что в олимпиадах очень важно.
    Ответ написан
    6 комментариев
  • Как вычислить число итераций алгоритма Евклида через вычитание?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Алгоритм через деление с остатоком - это всего-лишь оптимизация алгоритма через вычитание. Просто куча вычитаний делается скопом. Так же можно их все и подсчитать. Просто прибавляйте каждый раз не 1, а сколько там раз меньшее число в большее помещается. Типа answer += b/a
    Ответ написан
  • Как найти все способы представить число в виде произведения чисел Фибоначчи?

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

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

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

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

    Оценку можно вывести посчитав различные вызовы функции. Сначала, когда node->key не в интервале - будет ровно один рекурсивный вызов. Ну, потому что не в интервале, это или <min, или >max. Т.е. одно из двух условий (node->key > min, node->key < max) точно нарушается.

    Поэтому сколько-то уровней дерева будет ровно один вызов. Потом будут вызовы в вершины внутри интервала, и могут быть вызовы за границы интервала. Первые все посчитаются в слагаемом M в оценке. Сколько будет вторых? Не более 2*количество уровней - максимум один вызов в числа меньшие min и один в числа, большие max.

    Вот мы оказались в какой-то вершине в отрезке (A). И пошли от нее влево в вершину вне отрзка (B). А так же вправо в вершину (C). Так вот, все ключи в поддереве С не меньше A, а значит, ни одна из них не будет "торчать" из отрезка влево. Поэтому, "плохие" вершины вне отрезка слева, которые мы посещаем, там не встретятся никогда. Значит, на следующем уровне может быть максимум одна "плохая" вершина слева - правый ребенок B. И так далее, пока мы не спустимся до вершины опять в отрезке. Аналогичное рассуждение показывает, что может быть только по одной плохой вершины справа на каждом уровне.
    Ответ написан
    Комментировать
  • Как получить все возможные комбинации?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    itertools.combinations. Скармливаете туда range индексов вашей строки. Получаете список из 1/2/сколько надо сделать замен индексов, на эти позиции ставьте ваши Z, Y.

    Вот набросок кода.
    Я не питонист, возможно есть более лаконичная реализация. Особенно мерзкий хак с перобразованием строки в list, потому что строки в питоне immutable.

    import itertools
    
    def GenerateAll(source, rep):
      for comb in itertools.combinations(range(len(source)), len(rep)):
        s = list(source)
        for (i, j) in enumerate(comb):
          s[j] = rep[i];
        yield "".join(s)
        
    print(list(GenerateAll("abcd", "XY")))
    # ['XYcd', 'XbYd', 'XbcY', 'aXYd', 'aXcY', 'abXY']
    Ответ написан
  • Как сделать побитовое умножение и сложение большого числа decimal?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вот эти побитовые сдвиги работли бы, если бы вы хранили число в двоичной системе счисления. Допустим, по 16 бит в каждом int. В таком виде вычисления побыстрее будут немного, но при выводе надо переводить число в 10-ую систему счисления. Судя по примеру ответа, вы храните по 3 десятичных знака в каждой ячейке. Т.е. у вас основание системы счисления 1000. И тут нужны сдвиги не битовые, а тысячные (которых не существует).

    Вообще, основной цикл умножения должен быть примерно такой:

    for (i = 0; i < len1; ++i)
      for (j = 0; j < len2; ++j) 
        c[i+j] += a[i]*b[j];


    И потом надо сделать все переносы. Вот тут и происходят сдвиг на i позиций (в системе счисления 1000) - это то самое +i в индексе у массива результата. И вместо выполнения прибавления, когда бит равен 1, тут происходит умножение на a[i]. В битовом случае оно как раз вырождается в умножение на 1 или 0 - прибавление или пропуск прибавления.

    Это работает даже если вы в массивах a,b храните по несколько бит в каждой ячейке, или даже десятичных знаков. Это просто умножение в столбик в произвольной системе счисления.

    Правда, тут проблема, что вот такое вот умножение, если вы храните по 32 бита, оно переполнится, и ответ надо в 64-битном типе получать. И даже если вы храните по 16 бит, то сумма может переполниться из-за большого количества слагаемых. Ну это решается выполнением переноса сразу же:
    const int BASE = 1000; // База системы счисления. Оно же максимальное число в ячейке +1.
    for (i = 0; i < len1; ++i) {
      int carry = 0;
      for (j = 0; j < len2; ++j) {
        carry += c[i+j] + a[i]*b[j];
        c[i+j] = carry % BASE; 
        carry /= BASE;
      }
      int i = len1+len2-1;
      while (carry > 0) {
        carry += c[i];
        c[i] = carry % BASE;
        carry /= BASE;
        ++i;
      }
    }
    Ответ написан
  • Почему поиск в ширину работает?

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

    Вообще тут не нужен обход в ширину, тут, кажется, достаточно цикла по всем вершинам.

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

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

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Линейная. O(N+M). Ибо там цикл по i от 0 до startMedianIndex и вся остальная мишура на i вообще никак не влияет. Больше циклов в программе нет. Не понимаю, что тут может быть непонятного.
    Ответ написан
    Комментировать
  • Как хешировать в хеш таблице узлы дерева?

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

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

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

    Или можно тупо записать вашу структуру данных в строку (Например, расставив скобки вокруг каждого поддерева вроде "(a(b)(ccc(dd))" - это узел a, у которого есть дети b и ccc, у последнего есть ребенок dd) и потом как угодно хешировать уже строку, тогда ничего самостоятельно реализовавать ничего вообще не надо (to_json и hash от строки возможно уже есть).
    Ответ написан
    2 комментария
  • Как лучше развернуть двумерный массив?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Если не обязательно делать поворт на месте, то вся суть алгоритма вот в этой одной строке:
    result[i][j] = arr[n-1-j][i];
    Надо только циклы прогнать по нужным границам, да массив нужного размера создать.

    Если матрица квадратная, то элементы сдвигаются по кругу в четверках - и это можно сделать без дополнительного массива . Можно делать сдвиг по кругу со временной переменной. Что-то вроде этого:
    tmp = arr[i][j];
    arr[i][j] = arr[n-1-j][i];
    arr[n-1-j][i] = arr[n-1-i][n-1-j];
    arr[n-1-i][n-1-j] = arr[j][n-1-i];
    arr[j][n-1-i] = tmp;


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

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

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

    Вот у вас есть гипотеза m=(l+r)/2 - вы хотите проверить, а как она соотносится с длинной круга. Допустим, вы уже прошли x метров и прошли k полных кругов. Уже только из этой информации можно оценить, какая длина круга может быть. Во-первых, убедитесь, сравните floor(x/m) и k. Если floor(x/m) > k вы уже знаете, что длина круга больше m, ведь для круга длины m и более коротких значений вы бы насчитали floor(x/m) или больше оборотов. Если floor(x/m) < k, то уже очевидно, что длина круга меньше m. Если же floor(x/m) = k, то спросите run m-x%m - сколько осталось пройти до финиша, если бы длина круга была равна m. Если вы получили в ответ 1, то значит круг не длинее m. В протвином случае круг строго длиннее m.
    Ответ написан
    4 комментария
  • В чем разница 2ух кодов?

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

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

    Сразу вижу 2 проблемы, вообще не обрабатывается случай num1.size() <= num2.size(). Во-вторых, в конце, где вы нормализуете число, если там окажется 11, где-то, то вы оставите в этом разряде 0, а не 1, как надо. Правда, при сумме двух чисел там 11 получится за длиной максимального числа не может появиться, ибо туда может прийти только +1 от переноса. Но код все-равно логически неверен.
    Ответ написан
    Комментировать
  • Можно ли быстрее чем за O(N) подсчитать сумму S(N,K,M) = sum i=0..N K*i%M?

    wataru
    @wataru Автор вопроса, куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Совершенно случайно наткнулся вот на это вот равнество: hermite's identity (в твитте с прикольным доказательством. Зацените).

    C помощью него наконец-то вывел способ подсчитать эти суммы за O(log n)! Я знал, что это можно сделать!

    Итак, во-первых, можно переключатся между суммами floor(i*k/m) и (i*k)%m через вот это равенство: floor(i*k/m) = (i*k - (i*k)%m) / m
    Это нам позже поможет. В сумме через floor можно сократить GCD(m, k) в них. В сумме через модуль можно сделать k %= m, если оно больше m, да и сократить полные "обороты" в n.

    Итак, можно допустить, что m > k, m >n, GCD(m, k) = 1, иначе преобразуем сумму к нужной форме и все упростим.

    Дальше, применим равенство hermite's, взяв x = i/m, n = k.

    Потом поменяем местами суммы. Под знаками суммы будет floor(i/m + t/k) (где t - новая переменная суммирования от 0 до k-1). Присмотритесь к этому выражению - там 2 числа меньших 1. Т.е. весь этот floor даст 1, только если t/k >= 1-i/m. Отсюда можно решить это неравнество, сдвинуть нижнюю границу суммирования и получить сумму единиц во внутренней сумме. Заменив ее всю на количество слагаемых там вылезает floor (t*m/k). Т.е. мы выразили сумму i*k/m через сумму t*m/k. Но мы же помним, что можно перейти к сумме модулей и там сократить множитель k в числителе. Таким образом мы вычисляем сумму для (m, k) через сумму для (k, m%k). Это точно такой же переход, как и в GCD, поэтому суммарно будет всего O(log n) итераций. Вообще, выкладки довольно нудные, ибо сумма для t*m/k будет не от 0 до какого-то n', а от n' до k-1. Но можно взять известное значение для суммы полного оборота (от 0 до k-1) и из нее вычесть сумму от 0 до n'-1. Эта сумма известна, потому что при GCD=1, она пройдется по всем остаткам в каком-то порядке.

    Формула примерно такая получается:
    FloorSum(n, k, m) = (m-1)*(k-1)/2 - (n1+1)*n1*(m/k)/2 + (n - m + 1)*(k-n1-1) - FloorSum(n1, m%k, k)
    n' = floor(((m-n)*k-1)/m)


    Вот код. Значения совпадают с тупым решением для всех чисел до 1000 и для миллиардов случайных чисел до миллиона.
    // sum i = 0.. n floor(i * k / m)
    // GCD(k, m) must be 1.
    // n < m
    // k < m
    long long FloorSumInternal(long long n, long long k, long long m) {
    	if (k == 0 || n <= 0) return 0;
    	if (m == 1) return k*n*(n+1)/2;
    	const long long n1 = ((m-n)*k - 1)/m;
    	long long ans = (m-1)*(k-1)/2 - (n1+1)*n1*(m/k)/2 + (n - m + 1)*(k-n1-1);
    	ans -=  FloorSumInternal(n1, m%k, k);
    	return ans;
    }
    
    
    // sum i = 0.. n floor(i * k / m)
    long long FloorSum(long long n, long long k, long long m) {
    	if (k == 0 || n <= 0) return 0;
    	if (m == 1) return k*n*(n+1)/2;
    
    	const long long d = Gcd(m, k);
    	m /= d;
    	k /= d;
    	if (k >= m || n >= m) {
    		// (n*k*(n+1)/2 - ModSum(n, k, m, d))/m;
    
    		const long long nn = (n+1)%m-1;
    		const long long num_full = (n+1) / m;
    		const long long kk = k % m;
    
    		long long ans = 0;
    		ans = (k*n*(n+1) - kk*(nn+1)*nn)/m - num_full * (m-1);
    		ans /= 2;
    		ans +=  FloorSumInternal(nn, kk, m);
    		return ans;
    	}
    	return FloorSumInternal(n, k, m);
    }
    
    
    
    // sum i = 0.. n (i * k) % m;
    long long ModSum(long long n, long long k, long long m)
    {
    	long long d = Gcd(k, m);
    	if (k == 0 || m == 1) return 0;
    	// (i*k) % m = i*k-floor(i*k/m)*m
    	k %= m;
    	long long num_full = (n+1) / m;
    	long long ans = num_full * (m-d) * m/2;
    	n = (n+1)%m-1;
    	if (n > 0) {
    		ans += ((long long) k)*(n+1)*n/2 - m*FloorSum(n, k/d, m/d);
    	}
    	return ans;
    }


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

    Edit: прилизал код немного.
    Ответ написан
    Комментировать
  • Как получается формула N*(N-1) / 2?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Ну вы подставьте в формулу число. Допустим, N=3: 1+2+3 = 6. N(N-1)/2= 3*2/2 = 3. Не сходится!
    Правильная формула, все-таки N(N+1)/2.

    А разница в том, что в учебнике получается сумма от 1 до N-1. Т.е. в формулу прогрессии от 1 до N надо подставить N-1. Вот и получается (N-1)(N-1+1)/2 = (N-1)N/2
    Ответ написан
    1 комментарий
  • Как быстро умножают края матриц?

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

    Или можно наивно последние 1-7 слагаемых в каждой сумме подсчитать. Это займет O(n^2), что по сравнению с остальным алгоритмом - копейки.
    Ответ написан
    2 комментария
  • Как найти последовательность букв через графы?

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

    Тогда постройте граф на n*m*l вершин. Каждая вершина соответствует тройке (x, y, k) и означает, что мы как-то походили по полю и оказались в координате (x, y), при этом набрав первые k символов искомой строки. Переходы в графе тут такие: от каждой вершины можно пойти в 4 соседние по координатам, а количество символов увеличивается на 1, если в следующей вершине читается следующая буква строки. Т.е. переходы вида (x, y, k) -> (x+1, y, k + (s[k] == grid[x+1][y] ? 1 : 0)); (x, y, k) -> (x, y+1, k + (s[k] == grid[x][y+1] ? 1 : 0)) и еще 2 на минус (если x,y не у стенки поля, естественно - некоторые переходы могут отсутствовать).

    Ищите кратчайшее расстояние из вершины (x0, y0, s[0] == grid[x0][y0] ? 1 : 0) в любую вершину с третим индексом равным l (т.е. любое место, где вы прочтете всю искомую строку). Поскольку в графе все ребра длины 1, можно запускать bfs. Граф строить и охранить не надо, можно эти 4 перехода вычислять неявно. Кладите в очередь тройки чисел, вынимайте их оттуда и вычисляйте до 4 соседних троек, которые гужно тоже сложить в очередь обхода в ширину. Удобно завести 2 костантных массива на 4 элемента, которые будут хранить приращения по x и по y в каждую их четрех сторон. Тогда не будет много дублируемого кода.

    Еще понадобится, таки, массив на n*m*l для хранения расстояния, или хотя бы пометок о том, что вершина уже в очередь была положена.
    Ответ написан
    2 комментария
  • Как выполнить сортировку слиянием списков несравнимых элементов?

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

    Наверно, это задача на сортировку при частичном порядке.
    Решается через топологическую сортировку графа. Для каждой "буквы" заведите вершину. Проведите направленные ребра между каждой парой соседних элементов в каждом списке. Топологически отсортируйте граф. Работает за линейную сложность.
    Ответ написан
    1 комментарий
  • Логика игры "Пятнашки" на Python?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Надо, чтобы "четность" перестановки совпадала с четностью финального поля (1).
    Занумеруйте все 16 позиций слева направо сверху вниз.
    чтобы подсчитать четность, рассматривайте каждую пару заполненных позиций (15\*14/2=105 пар) - если числа идут не в том порядке (большее число на позиции с меньшим номером) - то прибавьте 1 к ответу. В конце возьмите ответ по модулю 2. Это и будет четность перестановки.

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

    Edit: Но вы это почти все итак знатете, ибо функция is_solvable в вашем коде как раз инверсии уже считает.
    Значит, Но вы знаете, что плохое поле от хорошего отличается лишь четностью, значит, если поле плохое - меняйте местами 2 соседних по порядку элемента. Например верхний левый со вторым в верхней строке.
    Ответ написан
    Комментировать