Введемо поняття маски, що описує підмножину предметів, які пересилаються. Наприклад, маска mask = 5 = 101_2 визначає підмножину, що включає нульовий і другий предмети (предмети нумеруються з нуля).
Для кожної підмножини обчислимо його сумарну вагу, яка визначає вартість пересилання. Якщо ця вага дорівнює рівно грамів, то, оскільки p \le 1000, завжди вигідно скористатися спеціальною пропозицією.
Нехай MinCost[mask] позначає мінімальну вартість (в євро) пересилання підмножини предметів, заданої маскою . Тоді для будь-якої маски , яка є підмножиною , виконується таке співвідношення:
Оскільки маска 2^n - 1 відповідає всій множині з предметів, відповіддю на задачу буде значення MinCost[2^n - 1].
Теорема. Перебір усіх масок і всіх їх підмасок виконується за O(3^n).
Доведення 1. Розглянемо -тий біт. Для нього можливі три варіанти:
він входить в маску і входить в підмаску ;
він входить в маску , але не входить в підмаску ;
він не входить ні в маску , ні в підмаску ;
Загалом бітів, отже загальна кількість різних комбінацій дорівнює 3^n.
Доведення 2. Нехай маска містить встановлених бітів. Тоді вона має рівно 2^k підмасок. Кількість масок довжини з рівно встановленими бітами дорівнює C_n^k. Отже, загальна кількість комбінацій дорівнює
Реалізація алгоритму
Маси предметів зберігатимемо в масиві . У ячейці 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];Для маски необхідно перебрати всі її підмаски і знайти мінімум серед усіх можливих значень
Через симетрію цієї суми досить розглядати тільки ті підмаски , для яких виконується умова 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));