ЗАДАЧИ
problems.ru |
О проекте
|
Об авторах
|
Справочник
Каталог по темам | по источникам | |
|
Задача 102943
УсловиеУважаемые господа! Сегодня вам предлагается для каждого из следующих типов комбинаторных объектов:1) перестановки N-элементного множества (лексикографический порядок); 2) K-элементные подмножества N-элементного множества (лексикографический порядок); 3) разбиения N-элементного множества на K непустых подмножеств (лексикографический, т.е. алфавитный, порядок); 4) разбиения числа N на слагаемые; 5) правильные скобочные последовательности из 2N скобок; 6) двоичные деревья с N вершинами; 7) цепочки из нулей и единиц длины N без двух единиц подряд; 8) перестановки N-элементного множества (порядок, в котором соседние перестановки отличаются транспозицией соседних элементов); 9) K-элементные подмножества N-элементного множества (порядок, в котором соседние подмножества отличаются двумя элементами); 10) все подмножества N-элементного множества (порядок, в котором соседние подмножества отличаются добавлением или удалением одного элемента); 11) подвешенные деревья с N вершинами; решить следующие две подзадачи: найти общее количество объектов и породить M объектов, начиная с L-го; по заданным объектам получить их номера. В качестве N-элементного множества везде подразумевается множество {1, ..., N}. Там, где порядок порождения комбинаторных объектов не указан, Вы можете выбрать его по своему усмотрению. Нумерация объектов начинается с нуля. Таким образом, Вам предстоит написать 11 программ. Задача
засчитывается, если Ваша программа прошла все тесты, в противном случае
РешениеДля каждого упомянутого в условии типа комбинаторных объектов необходимо научиться решать следующие задачи (считаем, что порядок объектов выбран):1. Определить количество объектов. 2. Найти объект по номеру. 3. Найти номер по объекту. 4. Получить по объекту следующий. Хотя последний пункт и сводится к двум предыдущим, существенно эффективнее будет процедура, решающая подзадачу 1 с помощью пункта 4. Рассмотрим принципы решения сформулированных задач на примере перестановок, разбиений на слагаемые и цепочек из нулей и единиц без двух единиц подряд (чтобы не писать каждый раз столько слов, назовем их просто хорошими). Перестановки (лексикографический порядок) Количество перестановок из N элементов равно N! – это известная комбинаторная формула. Порождение всех перестановок из N элементов будем проводить «по индукции». Предположим, что мы умеем порождать все перестановки из N-1 числа. Тогда сначала сгенерируем все перестановки чисел 2, ..., N, приписывая к ним слева единицу. Затем сгенерируем все перестановки чисел 1, 3, ..., N, приписывая к ним слева двойку, и т.д. Последним шагом будет порождение всех перестановок чисел 1, ..., N-1 с приписыванием к ним слева числа N. Можно, конечно, запрограммировать этот метод напрямую. Но давайте проанализируем его более внимательно. Из описанной процедуры вытекает, что каждая следующая перестановка получается из предыдущей таким образом. В «предыдущей» перестановке x1, ..., xn справа ищется наибольшая убывающая подпоследовательность чисел xi > ... > xn (если рассматривать позиции, начиная с i-ой, то это последняя перестановка чисел xi, ..., xn). Если i = 1, то все перестановки были сгенерированы. Иначе среди чисел x i , ..., x n выбирается наименьшее xj, большее xi-1, и ставится на позицию i-1, а все оставшиеся числа (и xi-1 в том числе) выписываются в возрастающем порядке. Таким образом, мы научились эффективно генерировать все перестановки, а
заодно и решили задачу получения по перестановке следующей. Пример работы
нашего алгоритма для N = 4 приведен на рисунке.
Алгоритм получения перестановки по ее номеру реализуется аналогично:
сначала определяем первую цифру перестановки, деля номер на (N-1)! и
прибавляя 1, затем вторую, деля остаток от предыдущего деления на
(N-2)!, и т.д.
Разберем этот метод подробнее. В процессе генерирования перестановок
над каждым числом будет записана стрелка, которая указывает, куда мы это
число в данный момент тащим (влево или вправо). В начальный момент
порождается перестановка 1, 2, ..., N, а все стрелки указывают вправо. Затем мы
берем самый младший элемент нашего множества – единицу – и тащим его по
направлению его стрелки (вправо) до тех пор, пока он не «упрется», т.е. не
достигнет последней возможной позиции. После этого мы меняем направление
стрелки над единицей, берем следующий по старшинству элемент – двойку –
и сдвигаем его на одну позицию по стрелке. Далее опять тащим единицу, но уже
в противоположном направлении, и т.д. То есть для получения следующей перестановки на очередном шаге мы
берем единицу и пробуем тащить ее по стрелке. Если она уперлась, то меняем ее
стрелку и пробуем тащить двойку. Если и она уперлась, меняем ее стрелку, и
Рассмотрим теперь, как по номеру K получить K-ую перестановку. Соответствующий алгоритм легко следует из метода генерирования. Ясно, что если перестановка чисел 2, ..., N имеет четный номер, то единица тащится вправо, иначе – влево. Следовательно, мы должны взять перестановку чисел 2, ..., N с номером [K / N] и вставить в нее единицу на позицию (K mod N) + 1. При этом позиция отсчитывается справа или слева в зависимости от четности числа [K / N]. Для вычисления по перестановке ее номера обозначим через L номер
перестановки чисел 2, ..., N, полученной из исходной удалением единицы.
Определив четность L, мы узнаем, в каком направлении единица тащится. Тем
самым, нам станет известно, справа или слева отсчитывать ее позицию. Пусть
номер этой позиции M. Тогда искомый номер перестановки будет равен
L·N
+
M
-
1.
Перейдем к задаче генерирования (N, K)-разбиений. Каждое разбиение единственным образом записывается в виде последовательности чисел, расположенных в порядке невозрастания. Будем порождать (N, K)-разбиения в антилексикографическом порядке, т.е. порядке, обратном алфавитному (рисунок демонстрирует этот порядок для (7, 5)-разбиений). Для этого, согласно предыдущим рассуждениям, необходимо сгенерировать сначала все (N-K, K)-разбиения, приписывая к каждому из них слева число K, а затем сгенерировать все (N, K-1)-разбиения. Как получить по (N, K)-разбиению следующее? Каждое разбиение выглядит таким образом: сначала идут числа, большие единицы, а затем – только единицы. Пусть L – количество единиц в разбиении. Найдем первое число справа, большее единицы, и обозначим его через x. Тогда следующее разбиение будет таким: все числа до x останутся теми же самыми, вместо x будет стоять x-1, а дальше – первое разбиение числа L+1 на слагаемые, не превосходящие x-1. Рассмотрим задачу вычисления по (N, K)-разбиению его номера. Пусть x – первое число заданного разбиения. Тогда все разбиения с первым числом от x+1 до min(K, N) предшествуют нашему. Их общее количество num равно (докажите это!) A(N - (x+1), x+1) + A(N - (x+2), x+2) + ... + A(N - min(K, N), min(K, N)). Пусть P – номер того разбиения числа N-x на слагаемые, не превосходящие x, которое получается из исходного вычеркиванием первого числа. Тогда номером исходного разбиения будет num+P. Аналогично осуществляется построение разбиения по номеру: определяем
сначала первое число разбиения, затем второе и т.д.
Из приведенного метода вытекает следующий способ получения следующей
в лексикографическом порядке хорошей цепочки. В имеющейся цепочке
выделяем справа максимальную подцепочку вида 1010...1 или вида
1010...10, ставим перед ней единицу, а все числа правее этой единицы
заменяем на нули.
Источники и прецеденты использования |
© 2004-...
МЦНМО
(о копирайте)
|
Пишите нам
|