Поштове відправлення#2509

Поштове відправлення (Розбір)

Введемо поняття маски, що описує підмножину предметів, які пересилаються. Наприклад, маска mask = 5 = 101_2 визначає підмножину, що включає нульовий і другий предмети (предмети нумеруються з нуля).

Для кожної підмножини обчислимо його сумарну вагу, яка визначає вартість пересилання. Якщо ця вага дорівнює рівно грамів, то, оскільки p \le 1000, завжди вигідно скористатися спеціальною пропозицією.

Нехай MinCost[mask] позначає мінімальну вартість (в євро) пересилання підмножини предметів, заданої маскою . Тоді для будь-якої маски , яка є підмножиною , виконується таке співвідношення:

MinCost[mask] = \min_{x \sub mask} (MinCost[mask~xor~x] + MinCost[x])

Оскільки маска 2^n - 1 відповідає всій множині з предметів, відповіддю на задачу буде значення MinCost[2^n - 1].

Теорема. Перебір усіх масок і всіх їх підмасок виконується за O(3^n).

Доведення 1. Розглянемо -тий біт. Для нього можливі три варіанти:

Загалом бітів, отже загальна кількість різних комбінацій дорівнює 3^n.

Доведення 2. Нехай маска містить встановлених бітів. Тоді вона має рівно 2^k підмасок. Кількість масок довжини з рівно встановленими бітами дорівнює C_n^k. Отже, загальна кількість комбінацій дорівнює

\sum_{k=0}^n C_n^k 2^k = (1 + 2)^n = 3^n

Реалізація алгоритму

Маси предметів зберігатимемо в масиві . У ячейці MinCost[mask] зберігатимемо мінімальну вартість пересилання підмножини предметів, заданої маскою .

int m[15], MinCost[1 << 14];

Читаємо вхідні дані.

scanf("%d %d",&n,&p);
for (i = 0; i < n; i++) scanf("%d",&m[i]);

У змінній перебираємо всі можливі маски від до 2^n - 1.

for (mask = 0; mask < (1 << n); mask++)
{

У змінну занесемо сумарну вагу підмножини предметів, що відповідає масці . Для цього переглянемо двійкове представлення числа : якщо на позиції стоїть , то додамо до вагу -го предмету.

  for (Cost = i = 0; i < n; i++)
    if (mask & (1 << i)) Cost += m[i];

Якщо значення строго дорівнює , то, оскільки за умовою p \le 1000, присвоюємо змінній значення .

  if (Cost == 1000) Cost = p;

Записуємо обчислену вартість пересилання у відповідну ячейку масиву .

  MinCost[mask] = Cost;
}

Для кожної маски перебираємо всі підмножини x \sub mask (маска є підмножиною тоді і тільки тоді, коли mask~AND~x дорівнює ). Якщо з множини, що відповідає масці , вичитати множину, що відповідає , то вийде множина з маскою mask~XOR~x.

Наприклад, якщо mask = 1101_2, x = 1001_2, то mask~XOR~x = 100_2, що відповідає теоретико-множинній операції \{1, 3, 4\} \setminus \{1, 4\} = \{3\}.

for (mask = 0; mask < (1 << n); mask++)
  for (x = 0; x < mask; x++)

Перераховуємо значення MinCost[mask] згідно з рекурентним співвідношенням, наведеним в аналізі задачі.

    if ((mask & x) == x) 
      MinCost[mask] = min(MinCost[mask],MinCost[mask^x] + MinCost[x]);

Виводимо відповідь, яка знаходиться в ячейці MinCost[2^n - 1].

printf("%d\n",MinCost[(1 << n) - 1]);

Реалізація з оцінкою O(3^n)

Оголосимо константу та масиви.

#define MAX 0xFFFFFFFF
unsigned int m[15], MinCost[1 << 14];

Для маски необхідно перебрати всі її підмаски і знайти мінімум серед усіх можливих значень

FindCost(sub) + FindCost(mask~XOR~sub)

Через симетрію цієї суми досить розглядати тільки ті підмаски , для яких виконується умова sub \ge mask~XOR~sub.

unsigned int FindCost(int mask)
{
  if (MinCost[mask] != MAX) return MinCost[mask];
  // sub перебирає всі підмаски маски mask
  for (int sub = (mask - 1) & mask; 
           sub >= mask ^ sub; sub = (sub - 1) & mask)
    MinCost[mask] = 
            min(MinCost[mask], FindCost(sub) + FindCost(mask ^ sub));
  return MinCost[mask];
}

Основна частина програми. Установимо MinCost[mask] = -1 для всіх масок, вартість відправки відповідного набору предметів для яких ще не була обчислена.

memset(MinCost,0xFF,sizeof(MinCost));

Читаємо вхідні дані.

scanf("%d %d",&n,&p);
for(i = 0; i < n; i++) scanf("%d",&m[i]);

Якщо вартість деякої підмножини предметів не перевищує (саме не більше, а не менше, так як можливий випадок p = 1000), то спеціальна пропозиція для їх відправки застосувати не можна. Для таких підмножин можна відразу обчислити MinCost[mask] як суму вартостей всіх предметів. Якщо сума вартостей предметів дорівнює рівно , то присвоюємо MinCost[mask] = p.

Таким чином ми попередньо заповнюємо відомі мінімальні вартості для малих підмножин. Це збільшує ефективність роботи програми.

for (mask = 0; mask < (1 << n); mask++)
{
  for(Cost = i = 0; i < n; i++)
    if (mask & (1 << i)) Cost += m[i];
  if (Cost == 1000) Cost = p;
  if (Cost <= 1000) MinCost[mask] = Cost;
}

Виводимо відповідь. Усі множини предметів задаються маскою 2^n - 1, двійкове представлення якої складається з одиниць.

printf("%u\n",FindCost((1 << n) - 1));