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

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Правильный ответ 8!. Ваше второе рассуждение упускает то, что вы одну и ту же позицию получите 8! раз. Ибо вы там считаете все фигуры уникальными. Допустим это все ладьи по диагонали. Вы первую можете поставить в 8 мест - одно из 64. Вторую в 7, когда выбираете из 49... И т.д. Вот и получится, что одну позицию - все на диагонали - вы подсчитали 8! раз.
    Ответ написан
    6 комментариев
  • Инструмент для сохранения всех вариантов сочетаний по заданной маске?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Есть. Называется python + модуль itertools.
    import itertools
    import string
    
    def generate_strings(length):
        chars = string.ascii_letters + string.digits
        for item in itertools.product(chars, repeat=length):
            yield "".join(item)
    
    for string in generate_strings(3):
        print(string)


    Для 8 символов поменяйте 3 в коде на 8.
    Ответ написан
    Комментировать
  • Удалить из ряда элементы ,как?

    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
    Разработчик на С++, экс-олимпиадник.
    Если пароли считаются уникальные, как строки, то ответ - 2^n: любую строку из "a" и "b" можно как-то составить.

    Если пароли считаются уникальные, как последовательность "aa", "a", "b" и "bb" (так, для трех букв отдельно подсчитаются a+aa, aa+a и a+a+a), то ответ можно найти по рекуррентной формуле F(N) = 2(F(N-1)+F(N-2)), F(1) = 2, F(2) = 6.

    Формла выводится так: В конце может быть один символ. Тут 2 варианта - это "a" или "b", тогда получается F(N-1) последовательностей длины N-1. Если же в конце идет "aa" или "bb", то таких последовательностей ровно F(N-2). Просуммировав все получится 2(F(N-1)+F(N-2))

    Это что-то вроде чисел фиббоначи. Можно еще вывести формулу:
    F(N) = (1+sqrt(3))^(N+1)-(1-sqrt(3))^(N+1) / (2sqrt(3))


    Если пишите программу, то лучше считать F() итеративно. Только учтите, там будут очень большие числа - в 64-битный тип не поместится.
    Ответ написан
    Комментировать
  • Как в js равномерно распределить комбинации пунктов в массиве, который был создан с помощью рекурсивного размещения?

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

    Допустим количество работ (N) и количество людей (M) взаимно просты.

    Тогда сгенерируйте M строчек беря людей подряд:
    1, 2, 3
    4, 5, 1
    2, 3, 4
    5, 1, 2
    3, 4, 5

    Тут каждый человек одинаковое количество раз (по одному разу) будет на каждой работе. И дни, когда он будет работать будут максимально равномерно распределены (минимальное расстояние и максимальное между соседними работами будут различаться максимум на 1 и равны floor(N/M) и ceil(N/M)). Это идеальное с точки зрения равенства расписание. Но у него минус - частоты пар работников будут не одинаковыми. 1 гораздо чаще будет работать с 2 и 5, чем с 3 и 4.

    Теперь, если N и M не взяимно просты. Пусть D = GCD(N,M) - наибольший общий делитель.

    Разбейте всех людей на D групп по N'=N/D человек. N' и M взяимно просты, поэтому можно применить алгоритм выше к каждой группе.

    Дальше эти D расписаний надо перемешать. Для максимальной равномерности - сначала взять все первые строки всех расписаний, потом все вторые, и т.д.

    На i-ом месте будет день i / N' из расписания i % N' (если индексация с 0).

    Так, например, решение для 2 работ и 6 людей:

    N' = 3. 2 группы.
    В первой:
    1 2
    3 1
    2 3

    Во второй:
    4 5
    6 4
    5 6

    В итоге:
    1 2
    4 5
    3 1
    6 4
    2 3
    5 6
    Ответ написан
    Комментировать
  • Какой длины массив чисел можно "упаковать" по 3 элемента 25 комбинациями?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Пусть делящихся на 2 - x, а делящехся на 3 - y.

    Тогда количество способов "выбрать три числа так, чтобы
    среди них было как минимум одно четное число и хотя бы одно число, делящееся на 3" - x(x-1)y/2+x*y*(y-1)/2. Или мы берем 2 четных числа и одно, делящееся на 3. или наоборот.

    Теперь надо подобрать такие x и у, чтобы вот эта формула сверху дала 25. Ответом будет x+y.

    Можно или перебрать мелкие значения x и y и посмотреть, что 2 и 5 подойдут, или вывести это логически. Формулу можно факторизовать до x*y*(x+y-2)/2. Приравняем к 25 и домножим на 2: x*y*(x+y-2) = 5*5*2. Справа произведение трех простых чисел. Слева три неизвестных целых множителя. Значит надо лишь перебрать способы распихать эти три простых числа по трем множителям. x и у не могут быть 1 вместе, ибо 1*1*0 != 50. Если x=1, а y!=1, То надо там тоже видно, что решения для y нет. x+y-2 тоже не может быть 1, ведь кто-то из x и y будет точно хотя бы 5. Ну и остается тольковариант x=5, y=2, (x+y-2) = 5.

    Итого, ответ - 7.
    Ответ написан
    Комментировать
  • Как решить задачу по комбинаторике?

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

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

    Во-первых, если всего спрайтов можно сгенерировать меньше 16, то просто генерируйте все.

    Далее будем генерировать сразу все 16 спрайтов. Текущие спрайты будут иметь первые K слоев заполнеными и сгруппированы в отрезки по совпадению. Далее для каждого отрезка длины L, если L больше количества вариантов в текущем слое (N), то разбивайте отрезко на N примерно одинаковых по длине кусков. Все куски будут длины floor(L/N) и L%N из них будут на 1 длиннее. Если L < N, то выбирайте случайно L спрайтов в текущем слое и получайте кучу отрезков длины 1.

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

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

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

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    События A и B независимые. Поэтому искомая верятность = P(A)+P(B) - P(A и B) = P(A)+P(B)-P(A)*P(B)

    Осталось найти вероятность каждого события. С вероятностью 1/4 по первой теме попадется тот же вопрос, что и в первой попытке. Во второй и третьей теме - вероятность 1/5. Вот у вас 3 монетки несимметричные подкинуты. Вам осталось найти вероятность, что для A - ровно одна монетка будет решкой, а для B - что все монеты выпали орлом. Так понятнее?
    Ответ написан
  • Как решить задачу на вероятность, схема Бернулли?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    У вас ошибка: надо еще P30(0) прибавлять.

    Какой-то более простой формулы мне придумать не удалось, и она вряд ли есть. Если руками вывести формулы для маленького количества банков, то там все-равно получается полином n/2-ой степени, т.е. от 15 слагаемых никуда не уйти.

    Я думаю, в этой задаче у вас приняли бы просто символьную записть в виде суммы.

    Если нужно числа - можно написать программу, или считать на калькуляторе. Начинайте с 0.7^30. Делайте m+, домножайте на 3/7*30/1, потом на 3/7*29/2, и так далее (нажимая m+ каждый раз), пока 15 слагаемых всего не наберется.

    Или вбить в wolframalpha и получть 98.3%.
    Ответ написан
    Комментировать
  • Алгоритм для минимизации стоимости товаров от разных поставщиков, какие ресурсы изучить?

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

    Индикаторные переменные (0 или 1) - покупаете ли вы этот товар у этого продавца (x_ij).
    Еще переменные - платите ли этому продавцу за доставку (y_j).

    Ограничения:

    y_j >= x_ij
    sum_j (x_ij) = 1

    Целевая функция - мнинимизировать затраты: sum_ij x_ij*c_ij + sum_j y_j*d_j

    Потом решать каким-либо солвером (есть куча быстрых библиотек).

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

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

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

    Нужно или писать рекурсивную функцию, или итеративно дописывать ко всем элементам массива по одному элементу из сделеющего множества. Просто переведите этот код на с++.

    Рекурсивная функция вроде как должна быть более дружественная к аллокациям и по этому - быстрее.
    Ответ написан
    Комментировать
  • Как найти количество перестановок числа?

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

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

    Еще, в вашей формуле направшивается добавить пустое число ("") в ответ. Давайте разрешим его и вычтем в конце 1.

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

    Поэтому пусть f(a0,a1,...a9) - сколько есть способов расставить некторые из a0 нулей, a1 единиц, a2 двоек, и т.д. (ноль может идти в начале, число может быть пустым).

    Ответ к задаче f(a0,a1,...a9)-1-min(a0,1)*f(a0-1,a1,...a9). Последнее слагаемое считает варианты, начинающиеся с нуля. Оно не вычитается, если нулей в числе нет. -1 посередине вычитает пустое число из ответа (но ее нет в последнем слагаемом, ведь мы там еще 0 в начале приписываем и пустое число надо считать).

    Теперь самое интересное, формула для f(a0,a1,...a9). Замкнутой формулы я не нашел, но придумал, как считать алгоритмом.

    Можно все варинаты разделить по количеству символов в числе (n), от 0 до суммы a0...a9. Давайте подумаем, где могуть быть девятки? i <= a9 из них попадут в ответ. Они могут быть на C(n, i) позициях. Останется разместить остальные цифры на n-i позиций.

    Вырисовывается следующее динамическое программирование:

    F(i, n) - сколько способов разместить первые i цифр на n позициях (возможно, беря не все цифры).

    F(i,n) = sum (j=0..min(a[i-1], n)) F(i-1, n-j)*C(n, j)
    F(0, n>0) = 0
    F(i,0) = 1.
    Ответ - sum(k=0..a0+...+a9) F(9, k)

    У функции как бы неявный параметр массив a[] количеств всех цифр, но я его не включаю в параметры динамики, потому что по нему не надо мемоизировать.

    Не забудьте, что для подсчета второй динамики, для f(a0-1,...a9) надо будет полностью отчистить мемеоизацию, потому что массив a поменялся же.

    Этот алгоритм работает за O(9*n), где n - длина входного числа.

    Вот пример для входного числа 112: все цифры, которых в числе нет можно выкинуть из рассмотрения и работать с массивом a={2,1} (для всех десяти цифр слишком много писать).

    F(0,0) = 1
    F(0,1) = 0
    F(0,2) = 0
    F(0,3) = 0
    F(1, 0) = 1
    F(1,1) = F(0, 1)*C(1,0) + F(0,0)*C(1,1) = 1
    F(1,2) = F(0,2)*C(2, 0)+ F(0,1)*C(2,1) + F(0,0)*C(2,2) = 1
    F(1,3) = F(0,3)*C(3, 0)+ F(0,2)*C(3,1) + F(0,1)*C(3,2) = 0
    F(2,0) = 1
    F(2,1) = F(1,1)*C(1, 0) + F(1,0)*C(1,1) = 2
    F(2,2) = F(1,2)*C(2, 0) + F(1,1)*C(2,1) = 3
    F(2,3) = F(1,3)*C(3, 0) + F(1,2)*C(3,1) = 3

    Ответ F(2,3)+F(2,2)+F(2,1)+F(2,0) = 3+3+2+1= 9.

    Вычитаем 1 (пустое число) и получаем 8 чисел, как у вас в примере для 112.
    Ответ написан
    2 комментария
  • Как решить эту комбинаторную задачу?

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

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

    Вот и считайте, сколько вариантов поставить палку перед каждой единицей. Надо подсчитать расстояния между каждой парой единиц и перемножить. Если единиц вообще нет - ответ 0, если одна - то 1.

    Можно сделать за один проход, если запоминать, где стояла предыдущая единица.

    int64_t CountWaysToSplit(std::string s) {
      int last_one = -1;
      int64_t res = 1;
      for (int i = 0; i < s.length(); ++i) {
        if (s[i] == '1') {
          if (last_one >= 0) res *= i - last_one;
          last_one = i;
        }
      }
      return last_one >= 0 ? res : 0;
    }
    Ответ написан
    Комментировать
  • Как составить все возможные сочетания из элементов разных массивов размером N?

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

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

    Функция берет из текущего массива один из элементов в цикле и добавляет его к ответу и запускается рукурсивно от следующего массива. Еще функция не берет ничего из текущего массива и запускается рекурсивно.

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

    Псевдокод примерно такой (не php, чтобы не позориться):

    function Generate(result, array_index, arrays) {
      if(result >= len(arrays)) {
        print(result)
        return
      }
      if (min_length - len(result) < len(arrays)-array_index-1) {
        Generate(answer, array_index+1, arrays);
      }
      if (len(answer) < max_length) {
        for i = 0...len(arrays[array_index]) {
          answer.push_back(arrays[array_index][i]);
          Generate(answer, array_index+1, arrays);
          answer.pop_back();
        }
      }
    }
    Ответ написан
    Комментировать
  • Количество комбинаций чисел JavaScript?

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

    ans = 0;
    for (i = 1; i <= 3; ++i) {
      for (j = 1; j <= 3; ++j) {
        mn = max(n - i - j - 3, 1);
        mx = n - i - j - 1;
        if (mn <= mx)
          ans += mx - mn + 1;
      }
    }


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

    Но это если у вас параметры фиксированные (4 блока 1-3 символа). Если параметры могут меняться, то решение - динамическое программирование f(i,k) - сколько способов разбить первые i символов на k блоков.

    База: f(0,0) = 1, f(0, k>0) = 0, f (i>0, 0) = 0;
    Пересчет: f(i,k) = sum_{l=min_length...min(max_length, i)}(f(i-l,k-1)).
    Ответ: f(n, num_blocks).
    Ответ написан
    Комментировать
  • Комбинаторика в Java Script, как добавить точку во всех возможных комбинациях массива?

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

    Так для 3 символов "abc" будет вызвана функция для "ab", которая вернет [ab, a.b], дописав "c" и ".c" к каждому элементу мы получим [abc, ab.c, a.bc, a.b.c].
    Ответ написан
  • Карты, колода 36 карт 1 туз и одна черви?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Если у вас задача - что в выбраных наугад картах есть хотя бы один туз и хотя бы одна черва, то надо найти Count(>0 червей И >0 тузов). Можно это инвертировать, считая все плохие варианты, вычев их из всех вариантов:

    Count(>0 червей И >0 тузов) = Count() - Count(0 червей ИЛИ 0 тузов)

    Дальше, COUNT(A или B) можно разложить на Count(A) + Count(B) - count(A И B).

    Финальная формула для ответа:

    Count() - Count(0 червей) - Count(0 тузов) + Count(0 червей И 0 тузов)

    Фактически, это форомула включения-исключения. Но в итоговой формеле все просто счиатать:

    Count() = C(5,36) - все варинты: сочетания по 5 из 36.

    Count(0 тузов) = С(5, 32) - нельзя брать тузы

    Count(0 червей) = С(5, 27) - нельзя брать 9 червей

    Count(0 червей И 0 тузов) = С(5, 24) - нельзя брать 9 червей и 3 оставшихся туза.

    Подсчитайте через фаториалы и сложите с правильными знаками.

    Если же задача - ровно один туз и ровно одна черва, то тут 2 варианта. Или туз-черва взят, или это две разные карты.

    В первом случае оставшиеся 4 карты - любые из 24 карт не-тузов-не-черв, т.е. эта часть - C(4,24). Во втором случае, вы берете какой-то из 3 тузов, какой-то из 8 черв и оставльные 3 карты из не-тузов-не-червей, т.е. ответ 3*8*C(3,24). Обе части просуммируйте.
    Ответ написан
    Комментировать