БлогNot. Задача о пакете пакетов

Задача о пакете пакетов

Представьте, что Вы вернулись из магазина и, после размещения всех покупок, у Вас осталась куча полиэтиленовых пакетов, которые Вы затолкали друг в друга и получили пакет пакетов.

Вы понимаете, что существуют разные способы сделать это, например, 3 пакета можно разместить так:

((())) 

- русский стиль, все пакеты "матрёшкой" засунуты один в один.

(()()) 

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

Для 4 пакетов имеем уже 4 способа:

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

Вопрос: сколько есть способов получить пакет из n пакетов? Хотелось бы также напечатать все конфигурации, "скобочное" представление в этой задаче весьма удобно.

Математически каждая конфигурация из n пакетов - это дерево с n узлами, где пакет представляет собой узел дерева, а пакет с его содержимым образует поддерево. Самый внешний пакет - это корневой узел. К счастью, уже известна последовательность OEIS A000081, которая показывает количество наших деревьев с n узлами (n = 0, 1, ...). Видно, что значения элементов последовательности довольно быстро растут.

Ниже показана консольная программа на C++, которая выводит для заданного n все конфигурации пакетов и подсчитывает их количество. Если для больших значений n возникнет переполнение, возможно, нужно больше памяти для вектора offset в функции init. Программа не контролирует значение n и не работает при n, меньшем 1. Проверялась в актуальной сборке Visual Studio 2019.

#include <iostream>
#include <vector>
std::vector <long> treeList;
std::vector <int> offset;

void init (int n) {
 int n2 = n * 2 + 1;
 for (int i = 0; i < n2; i++) 
  offset.push_back( i == 1 ? 1 : 0);
}

void append (long t) {
 treeList.push_back(1 | (t << 1));
}

void show(long t, int l) {
 while (l-- > 0) {
  std::cout << (t % 2 == 1 ? '(' : ')');
  t >>= 1;
 }
}

void listTrees(int n) {
 int n1 = n + 1;
 for (int i = offset[n]; i < offset[n1]; i++) {
  show (treeList[i], 2 * n);
  std::cout << std::endl;
 }
}

void assemble (int n, long t, int sl, int pos, int rem) {
 if (rem == 0) {
  append(t);
  return;
 }
 auto pp = pos;
 auto ss = sl;
 if (sl > rem) {
  ss = rem;
  pp = offset[ss];
 }
 else if (pp >= offset[ss + 1]) {
  ss--;
  if (ss == 0) {
   return;
  }
  pp = offset[ss];
 }
 assemble (n, t << (2 * ss) | treeList[pp], ss, pp, rem - ss);
 assemble (n, t, ss, pp + 1, rem);
}

void makeTrees(int n) {
 if (offset[n + 1] != 0) return;
 if (n > 0) makeTrees(n - 1);
 assemble(n, 0, n - 1, offset[n - 1], n - 1);
 offset[n + 1] = treeList.size();
}

void test(int n) {
 append(0);
 makeTrees(n);
 listTrees(n);
 std::cout << "Number of " << n << "-trees = " << offset[n + 1] - offset[n] << std::endl;
}

int main() {
 const int n = 13;
 init(n);
 test(n);
 return 0;
}

07.11.2021, 14:29 [669 просмотров]


теги: c++ числа алгоритм

К этой статье пока нет комментариев, Ваш будет первым