Паркан для трави#1526

Паркан для трави (Розбір)

Площа трикутника зі сторонами a, b, c обчислюється у функції area за формулою Герона:

S = \sqrt{(p (p - a)(p - b)(p - c))},~де~p = (a + b + c) / 2

Нехай — деяка підмножина наявних шматків огорожі. Функція FindSol() знаходить максимальну площу, яку можна огородити за допомогою трикутників, складених із шматків цієї підмножини.

Змінна представляє собою маску підмножини: це -бітове число, -ий біт якого дорівнює , якщо підмножина містить шматок fences[i], і інакше.

Відповіддю на задачу буде значення FindSol(2^n - 1), де — кількість чисел у масиві .

Значення всіх можливих масок лежать в діапазоні від до 2^{16} - 1 (оскільки за умовою є не більше шматків огорожі). У комірці best[mask] зберігається максимальна площа, яку можна огородити підмножиною шматків огорожі, відповідною масці .

Для обчислення FindSol() слід перебрати всі можливі трійки шматків огорожі i, j, k, які присутні в . Для кожної трійки перевіряється нерівність трикутника, щоб переконатися, що з цих шматків можна скласти трикутник. Якщо трикутник можливий, обчислюється сума:

area(fences[i], fences[j], fences[k]) + FindSol(mask \ {i, j, k})

Далі перебираються всі такі трійки (i, j, k), і вибирається та, для якої вказана сума максимальна. Це максимальне значення присвоюється комірці best[mask]:

FindSol(mask) = \max_{\substack{(i,j,k) \in mask \\ 0 \le i < j < k < n}} (area(fences[i], fences[j],fences[k]) + FindSol(mask \setminus \{i, j, k\}))

Якщо спочатку довжини шматків огорожі відсортувати, то при перевірці нерівності трикутника (сторони якого дорівнюють fences[i], fences[j], fences[k]), достатньо перевірити тільки одну умову:

fences[i] + fences[j] > fences[k]

Оскільки при сортуванні виконується нерівність

fences[i] \le fences[j] \le fences[k],

з цього завжди слідує, що

fences[i] + fences[k] > fences[j] і fences[j] + fences[k] > fences[i].

Приклад

У першому тесті оптимально побудувати трикутники зі сторонами (4, 5, 6) і (7, 8, 9). Сумарна огороджена площа в цьому випадку дорівнює .

У другому тесті відповідь дорівнює , оскільки з огород неможливо скласти жодного трикутника.

У третьому тесті слід побудувати трикутник зі сторонами (4, 4, 4). Зверніть увагу, що довжини огород можуть повторюватися.

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

Оголосимо глобальні змінні.

#define MAX 16
double best[1<<MAX];
int f[MAX+1];

Функція area обчислює площу трикутника за допомогою формули Герона.

double area(int a, int b, int c)
{
  double p = (a + b + c) / 2.0;
  return sqrt(p * (p - a) * (p - b) * (p - c));
}

Функція FindSol повертає максимальну площу, яку можна огородити за допомогою трикутників, складених із оград, включених у маску .

double FindSol(int mask)
{

Якщо значення в масиві best[mask] більше або дорівнює нулю, воно вже розраховане, і функція повертає це значення. Це дозволяє уникнути повторних обчислень і прискорює роботу алгоритму.

  if (best[mask] >= 0.0) return best[mask];

Ініціалізуємо best[mask] = 0.

  best[mask] = 0;

Перебираємо всі можливі трійки індексів (i, j, k) таких, що відповідні огради включені в .

  for (int i = 0; i < n - 2; i++)     if (mask & (1 << i))
  for (int j = i + 1; j < n - 1; j++) if (mask & (1 << j))
  for (int k = j + 1; k < n; k++)     if (mask & (1 << k))

Перевіряємо умову існування трикутника. Оскільки огради спочатку відсортовані, цієї умови достатньо, щоб трикутник існував.

    if (f[i] + f[j] > f[k]) 

Якщо трикутник існує, обчислюємо суму:

FindSol(mask~\hat{}~(1 << i)~\hat{}~(1 << j)~\hat{}~(1 << k))
      best[mask] = max(best[mask], area(f[i],f[j],f[k]) + 
          FindSol(mask ^ (1 << i) ^ (1 << j) ^ (1 << k)));
  return best[mask];
}

Основна частина програми. Читаємо кількість оград .

while (scanf("%d",&n) == 1)
{
  memset(f,0,sizeof(f));

Читаємо довжини оград і сортуємо їх за неубуванням.

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

Ініціалізуємо масив .

  for (i = 0; i < (1 << MAX); i++) best[i] = -1.0;

Обчислюємо і виводимо відповідь.

  printf("%.4lf\n",FindSol((1 << n) - 1));
}