8. Перестановки
Сложная задача на динамическое программирование, представление множеств в виде двоичных чисел.
Для нахождения НОД двух чисел используем следующую функцию.
function gcd(a,b:longint):longint;
begin
if b=0 then gcd:=a
else gcd:=gcd(b, a mod b);
end;
Чтобы ускорить вычисления, можно заполнить результаты вычисления НОД для элементов множества в таблице.
var
_gcd:array[1..16,1..16]of longint;
...
for i:=1 to n do
for j:=1 to n do
_gcd[i,j]:=gcd(p[i],p[j]);
Использование при реализации алгоритма динамического программирования запоминающей ранее вычисленные результаты функции позволяет не заботиться о правильном порядке заполнения таблицы с результатами для отдельных подзадач.
Функция count вычисляет количество `k`-перестановок подмножества из элементов, заданных параметром mask (в `j`-м бите числа стоит 1, если в подмножесто входит `(j+1)`-й элемент исходного множества), в которых на первом месте стоит `i`-й элемент исходного множества.
var
c:array[1..16,1..65535]of int64; { таблица с результатами подзадач }
...
function count(i,mask:longint):int64;
var j:longint;
begin
if c[i,mask]<0 then { значение еще не вычислялось }
begin
c[i,mask]:=0;
for j:=1 to n do
if (i<>j) and
((mask and (1 shl (j-1)))<>0) and { есть в подмножестве }
(_gcd[i,j]>=k) { НОД > k }
then
{ j-й элемент можно поставить следующим после i-го }
c[i,mask]:=c[i,mask]+count(j,mask and not(1 shl (i-1)));
end;
count:=c[i,mask];
end;
...
{ начальная заполнение таблицы }
mask:=(1 shl n)-1;
for i:=1 to n do
for j:=1 to mask do
c[i,j]:=-1; { вычисления не выполнены }
for i:=1 to n do
c[i,1 shl (i-1)]:=1; { существует только одна перестановка из одного элемента }
После ввода необходимо отсортировать элементы множества, чтобы рассматривать перестановки в лексикографическом порядке.
var
p:array[1..16]of longint;
n,m,k,t,i,j,mask:longint;
...
read(n,m,k);
for i:=1 to n do
read(p[i]);
for i:=1 to n do
for j:=i+1 to n do
if p[i]>p[j] then
begin
t:=p[i];
p[i]:=p[j];
p[j]:=t;
end;
После подготовительных операций выбираем, с какого элемента должна начинаться перестановка. Если количество перестановок, начинающихся с `j`-го элемента меньше `m`, рассматриваем в качестве кандидата `(j+1)`-й элемент, при этом из `m` вычитаем количество `k`-перестановок, начинающизся с `j`-го элемента. Если количество перестановок, начинающихся с `j`-го элемента больше или равно `m`, печатаем `j`-й элемент и убираем его из множества. Алгоритм выполняется, пока множество не опустеет, либо не будет найден ни один кандидат на первое место в перестановке.
var fl:boolean;
...
i:=0; { предыдущий элемент перестановки }
while mask<>0 do { пока не выведем все элементы из множества }
begin
fl:=false;
for j:=1 to n do
begin
if (i<>j) and
((mask and (1 shl (j-1)))<>0) and { есть в подмножестве }
((i=0) or (_gcd[i][j]>=k)) { НОД > k или 1 элемент перестановки }
then
begin
if m<=count(j,mask) then
begin { попали на нужный элемент }
write(p[j],' ');
i:=j;
mask:=mask and not(1 shl (j-1));
fl:=true;
break;
end
else
m:=m-count(j,mask);
end;
end;
if not fl then { нет подходящего элемента }
begin
writeln(-1);
halt(0);
end;
end;
writeln;