Размещения с повторениями

advertisement
Размещения с повторениями
Рассмотрим последовательности из трех чисел, в которых каждый член –
целое число в диапазоне от 0 до 2. Как перебрать все такие последовательности?
Для решения этой и подобных задач перебора всех объектов из некоторого
множества часто применяют такой подход: сначала упорядочивают все элементы
данного множества, а потом учатся переходить от одного элемента к следующему.
Тогда перебор всех последовательностей можно осуществлять по следующей
схеме:
обрабатываем первую последовательность
обрабатываем следующую за ней последовательность
обрабатываем следующую за ней последовательность
…
Как же упорядочить все наши последовательности? Представим на минуту,
что у нас есть не последовательности чисел, а наборы букв (“слова”). Тогда один из
естественных способов упорядочить их – воспользоваться алфавитом и
расположить их в такой последовательности, в какой они встречаются в словаре
(такой порядок называют лексикографическим). Теперь давайте вернемся к числам.
Упорядочим их также “по алфавиту”, считая "алфавитом" числа 0, 1, 2 (именно в
таком порядке). Сначала будут идти последовательности, начинающиеся с нуля,
затем – последовательности, начинающиеся с единицы, и в конце –
последовательности, начинающиеся с двойки. Среди всех последовательностей,
начинающихся с нуля, вначале будут идти последовательности, в которых второй
член – 0, затем те, у которых второй член – 1, и затем – 2. Вот как в итоге будет
выглядеть полный список последовательностей:
000 001 002 010 011 012 020 021 022 100 101 102 110 111
112 120 121 122 200 201 202 210 211 212 220 221 222
Упорядочив все последовательности, мы можем перейти к следующему
этапу решения задачи: научимся по данной последовательности получать
следующую. Итак, рассмотрим некоторую последовательность. Если последняя
цифра данной последовательности – не 2, то для получения следующей
последовательности достаточно просто увеличить последнюю цифру на 1:
например, 021 → 022, или 110 → 111. Если же последовательность заканчивается
на 2, то нужно заменить последнюю двойку и все стоящие перед ней двойки на
нули, а самую правую цифру, отличную от 2, увеличить на 1: например, 022 → 100.
Теперь, пользуясь вышеизложенным, можно составить алгоритм для
перебора всех размещений с повторениями (так называют рассматриваемые нами
последовательности: мы размещаем на заданных местах данные цифры, причем
любая цифра может повторяться – встречаться несколько раз): берем самое первое
размещение (000), и строим по нему следующее. Для вновь полученной
последовательности строим следующую и т. д. Эту процедуру повторяем до тех
пор, пока не дойдем до последнего размещения (222).
Возникает справедливый вопрос: когда нужно остановиться? Напрашивающийся
ответ “Когда дойдем до последовательности 222” не вполне удовлетворительный:
ведь если взять более длинные последовательности, то на проверку того, что все
члены последовательности являются двойками, на каждом шаге будет уходить
слишком много времени. Проще заранее вычислить, сколько всего шагов нам
нужно сделать. А для этого нам необходимо знать, сколько всего существует
размещений с повторениями для заданного количества мест N и заданного
количества чисел K, которые мы можем поставить на каждое место.
Итак, рассмотрим имеющиеся у нас N позиций. На первую позицию мы можем
поставить любое из K чисел. Какое бы число мы не поставили на первое место, на
второе место мы опять можем поставить любое из K чисел, таким образом, первые
два места мы можем занять K x K способами. Продолжая так и далее, получим, что
занять все позиции числами мы можем K x … x K = KN способами.
Количество последовательностей длины N из K различных символов равно KN.
Таким образом, чтобы перейти от первой последовательности к последней, нужно
совершить KN – 1 переход.
Ответим теперь на такой вопрос: как по последовательности узнать ее порядковый
номер, не перебирая при этом все последовательности до нее? Для этого
посмотрим внимательнее на наши последовательности. Заметим, что они
представляют собой числа в (K+1)-ричной системе счисления (с цифрами от 0
до K). Причем эти числа расположены в порядке возрастания: 0003 = 010, 0013 = 110,
0023 = 210, 0102 = 310, 0112 = 410 и т. д., то есть наш лексикографический порядок
совпадает с естественным порядком для натуральных чисел. Поэтому чтобы
вычислить номер размещения, достаточно перевести число в десятичную систему и
прибавить 1 (поскольку первое число равно 0, а мы хотим начинать считать с
единицы).
Теперь легко дать ответ и на вопрос обратной задачи: как по номеру
последовательности получить саму последовательность. Попробуйте сделать это
самостоятельно (не забудьте, что мы хотим начинать нумерацию с единицы, а не с
нуля!).
Вернемся опять к нашей первой задаче - перебору всех размещений - и изучим еще
один способ ее рещения. Воспользуемся следующей идеей: если мы выпишем все
размещения длины N–1, то сможем легко получить все размещения длины N,
приписывая к каждому выписанному размещению по очереди каждое из K чисел.
Реализуем этот алгоритм рекурсивно. Процедура Next (i : integer) будет
предполагать, что в элементах 1..i–1 массива а записано некоторое размещение, и
будет последовательно подставлять все допустимые числа в i-ю позицию, вызывая
после этого Next(i+1), если i<N и печатая получившееся размещение в случае i=N:
procedure Next(i:integer);
begin
for j:=0 to k-1 do begin
a[i]:=j;
if i<N then Next(i+1)
else Print;
end;
Теперь для печати всех размещений достаточно задать значения N и K и вызвать
Next(1):
begin
read(N,K);
Next(1);
end.
{Разбор работы алгоритма на примере}
Подведем некоторые итоги, которые помогут нам в дальнейших исследованиях
комбинаторных задач. Итак, мы выделили некоторое множество объектов
(размещения с повторениями) и ввели на нем некоторый порядок, то есть для
любых двух последовательностей научились определять по некоторому правилу,
какая их них "идет раньше". Мы также вычислили общее количество
рассматриваемых объектов. Далее мы научились по данной последовательности
находить следующую (согласно введенному порядку), и это позволило нам
перечислить все объекты. После этого мы научились по последовательности
определять ее номер (в указанном порядке) и, обратно, по номеру –
последовательность. Именно эти задачи и приходится решать при работе с
разнообразными комбинаторными объектами. Решению этих задач для
разнообразных структур и будут посвящены оставшиеся пункты этой главы.
Задачи
Задача 1. Напечатать все последовательности длины n из чисел в диапазоне от 0 до
k – 1 в лексикографическом порядке.
Входные данные
Два числа – n и k (1<=n<=10, 2<=k<=10, nk<=10000).
Выходные данные
В каждой строке вывести n чисел через пробел – запись соответствующего
размещения с повторением.
Пример
Входной файл
Выходной файл
2 3
0
0
0
1
1
1
2
2
2
0
1
2
0
1
2
0
1
2
Задача 2. Подмножеством данного множества называют любой набор элементов
из данного множества. При этом считается, что все элементы множества
различны, и что порядок элементов в подмножестве не имеет значения (то есть
{1,3} и {3,1} – это одно и то же подмножество множества {1,2,3}). Отметим, что у
любого множества есть подмножество, в котором нет ни одного элемента: {} (его
называют пустым), и подмножество, включающее все элементы данного
множества.
Требуется напечатать все подмножества множества {1,2,…,n}, исключая
пустое
Входные данные
Одно число n – натуральное число, не превосходящее 10.
Выходные данные
В каждой строке вывести сначала количество чисел в соответствующем
подмножестве, а затем сами эти числа. Выводить подмножества можно в любом
порядке, в каждом подмножестве числа должны быть упорядочены по
возрастанию.
Пример
Входной файл
2
Выходной файл
2 1 2
1 1
1 2
Задача 3. Напечатайте все последовательности из n натуральных чисел
(возможно, с повторениями), в которых i-й член не превосходит i.
Последовательности требуется вывести в лексикографическом порядке.
Входные данные
Одно число n – натуральное число, не превосходящее 8.
Выходные данные
В каждой строке вывести n чисел через пробел – запись соответствующего
размещения с повторением.
Пример
Входной файл
Выходной файл
3
1
1
1
1
1
1
1
1
2
2
1
2
3
1
2
1 2 3
Комментарий: на первом месте может стоять только число 1, на втором – 1 или 2,
на третьем – 1, 2 или 3, и т.д.
Задача 4. В написанном выражении ((((1 ? 2) ? 3) ? 4) ? 5) ? 6 вместо каждого
знака ? вставить знак одной из 4 арифметических операций +, –, *, : так, чтобы
результат вычислений равнялся заданному целому числу n (при делении дробная
часть отбрасывается).
Входные данные
Одно целое число n, не превосходящее 1000. Найти все решения.
Выходные данные
Вывести одно число – количество различных решений задачи. Если решений нет,
вывести 0.
Пример
Входной файл
3
Выходной файл
28
0
4
Комментарий
((((1+2)+3)+4)+5)+6
((((1*2)+3)*4)–5)+6
((((1*2)–3)+4)*5)+6
((((1*2)*3)+4)+5)+6
решений нет
Разборы
Задача 1. В этой задаче требуется реализовать алгоритм, подробно описанный
выше.
Задача 2. Сопоставим каждому подмножеству множества {1,2,…,n} набор из n
нулей и единиц по следующему правилу: первая цифра показывает, встречается ли
в подмножестве число 1 (0 – не встречается, 1 – встречается), вторая цифра
показывает, встречается ли в подмножестве число 2, и т. д. Например,
подмножеству {2,3,5} множества {1,2,3,4,5,6} соответствует последовательность
011010. Таким образом, каждому подмножеству соответствует некоторое
размещение нулей и единиц, и обратно, каждому такому размещению
соответствует некоторое подмножество. Таким образом, можно перебрать все
размещения и напечатать для каждого соответствующее ему подмножество.
Нам еще требуется печатать количество чисел в каждом подмножестве – оно равно
количеству единиц в соответствующем размещении.
{подробнее про подмножества}
Задача 3. Это тоже задача на перебор всех объектов из некоторого множества.
Применим к ней тот же подход: упорядочим все объекты и научимся по данному
объекту определять следующий. Порядок – лексикографический – нам уже указан в
условии. Алгоритм перехода к следующей последовательности похож на алгоритм
для размещений. Смотрим на последнюю цифру: если ее можно увеличить на 1, то
увеличиваем ее, и получаем следующую последовательность. В противном случае
заменяем ее единицей и переходим к числу слева от него. С ним проделываем ту же
операцию и т. д. пока не найдем число, которое можно увеличить на 1.
Задача 4. Эта задача демонстрирует применение алгоритма перебора размещений с
повторениями. На пять мест, отмеченных вопросительными знаками, можно
поставить любой из знаков арифметических операций, то есть нужно перебрать все
размещения с повторениями длины 5 из 4 символов, и для каждого такого
размещения проверить значение арифметического выражения, получающегося при
такой расстановке знаков.
Download