Нехай і — дві пляшки, які робот буде збирати послідовно. Після того як робот підніме пляшку , він повинен віднести її до однієї з чотирьох граней столу і викинути. Лише тоді робот може рушити за пляшкою . Знайдемо найкоротший шлях робота від точки A(x_i, y_i) до точки B(x_j, y_j) з обов'язковим заходом на край столу.
Відстань AK + KB = AK + KP = AP. Знаючи координати точок A(x_i, y_i) і P(x_j, -y_j), можна обчислити відстань
Відстань AL + LB = AL + LQ = AQ. Абсциса точки дорівнює w + (w - x_j) = 2w - x_j. Знаючи координати точок A(x_i, y_i) і Q(2w - x_j, y_j), можна обчислити відстань
Відстань AE + EB = AE + ET = AT. Знаючи координати точок A(x_i, y_i) і T(-x_j, y_j), можна обчислити відстань
Відстань AD + DB = AD + DS = AS. Ордината точки дорівнює l + (l - y_j) = 2l - y_j. Знаючи координати точок A(x_i, y_i) і S(x_j, 2l - y_j), можна обчислити відстань
Знайдемо найменшу відстань, яку потрібно пройти роботу, щоб викинути пляшку, розташовану в точці (x_i, y_i), за край столу. Ця відстань дорівнює мінімуму з чотирьох значень:
Саме такий шлях робот повинен буде пройти, щоб викинути останню пляшку.
Початкове положення робота і координати всіх пляшок задані. Розглянемо граф, у якому нульова вершина відповідає початковому положенню робота, а решта вершин — пляшкам. Відстань між нульовою вершиною і кожною з решти визначається як евклідова відстань між відповідними точками. Відстань між пляшками і задається як мінімальний шлях, який робот повинен пройти від пляшки до пляшки , обов'язково побувавши на межі столу. Оскільки на столі розташовано пляшок, граф міститиме n + 1 вершину.
Далі потрібно знайти в цьому графі Гамільтонів шлях мінімальної довжини. Після того як робот дійде до останньої пляшки, до загальної довжини шляху потрібно додати відстань, необхідну для викидання цієї пляшки за край столу. Пошук Гамільтонового шляху виконується за допомогою динамічного програмування з використанням масок, з асимптотичною складністю O(n^2 \cdot 2^n).
Приклад
Нижче наведено початкове положення робота і координати пляшок.
Довжина найкоротшого шляху робота дорівнює
Реалізація алгоритму
Оголосимо константу , що позначає безкінечність, і константу — максимальну кількість точок у Гамільтоновому шляху.
#define INF 1e18
#define MAX 20Оголосимо масив , у якому зберігатимуться значення dp(S, i), що перераховуються динамічно. Координати пляшок зберігаємо в точках (x_i, y_i)~(1 \le i \le n). Початкове положення робота зберігаємо в точці (x_0, y_0).
double dp[1 << MAX][MAX + 1];
double x[MAX], y[MAX], m[MAX][MAX];Функція solve обчислює значення dp(S, last) — мінімальну відстань, щоб пройти всі точки з множини , закінчивши на вершині . Множина кодується цілим числом . Тоді:
де
m[i][last] — відстань між точками і ,
S\setminus {last} = mask~\^{}~(1<< last) — множина без елемента .
Змінна відповідає комірці dp[mask][last], тому при зміні автоматично оновлюється і значення dp[mask][last].
double solve(int mask, int last)
{
double &res = dp[mask][last];
if (res == INF)
{
mask ^= (1 << last);
for (int i = 1; i < n; i++)
if ((mask & (1 << i)) && (i != last))
res = min(res, solve(mask, i) + m[i][last]);
}
return res;
}Функція f повертає найкоротшу відстань від точки (x[i], y[i]) до границі столу.
double f(int i)
{
return min(min(x[i], y[i]), min(l - y[i], w - x[i]));
}Функція dist повертає відстань між точками (x[i], y[i]) і (x[j], y[j]).
double dist(int i, int j)
{
return sqrt((x[i] - x[j])*(x[i] - x[j]) +
(y[i] - y[j])*(y[i] - y[j]));
}Основна частина програми. Читаємо розмір столу і .
scanf("%d %d", &w, &l);Читаємо кількість пляшок і їх координати (x[i], y[i]).
scanf("%d", &n);
for (i = 1; i <= n; i++)
scanf("%lf %lf", &x[i], &y[i]);Початкові координати робота зберігаємо в (x[0], y[0]).
scanf("%lf %lf", &x[0], &y[0]); До пляшок додаємо нульову вершину, що відповідає початковому положенню робота. Після цього збільшується на одиницю і стає рівним загальному числу вершин у графі. Вершини графа нумеруються від 0, 1, ..., n - 1.
n++;Обчислюємо попарні відстані між пляшками. Відстань між пляшками і визначається як мінімальний шлях, який робот повинен пройти від пляшки до пляшки з обов'язковим заходом на межу столу.
for (i = 1; i < n - 1; i++)
for (j = i + 1; j < n; j++)
m[i][j] = m[j][i] =
min(
min(sqrt((x[i] + x[j])*(x[i] + x[j]) + (y[i] - y[j])*(y[i] - y[j])),
sqrt((2 * w - x[i] - x[j])*(2 * w - x[i] - x[j]) +
(y[i] - y[j])*(y[i] - y[j]))),
min(sqrt((x[i] - x[j])*(x[i] - x[j]) + (y[i] + y[j])*(y[i] + y[j])),
sqrt((x[i] - x[j])*(x[i] - x[j]) +
(2 * l - y[i] - y[j])*(2 * l - y[i] - y[j])))
);Спочатку значення dp(S, i) невідомі, тому присвоюємо їм значення плюс нескінченність.
for (i = 0; i < 1 << n; i++)
for (j = 0; j < n; j++)
dp[i][j] = INF;Множині відповідає маска . Встановимо dp(\{0\}, 0) = 0, оскільки мінімальний шлях із нульової вершини в саму себе без відвідування інших вершин дорівнює нулю. У змінній будемо зберігати бажану мінімальну довжину Гамільтонова шляху.
dp[1][0] = 0; total = INF;
for (i = 1; i < n; i++) dp[1 | (1 << i)][i] = dist(0, i);Знаходимо Гамільтонів шлях мінімальної довжини. Значення 2n - 1 відповідає множині всіх вершин \{0, 1, 2, ..., n - 1\}. Обчислюємо мінімум серед усіх значень:
Після знаходження мінімальної довжини Гамільтонова шляху до результату потрібно додати відстань, яку робот проходить, щоб викинути останню -ту пляшку за межу столу — вона дорівнює .
for (i = 1; i < n; i++)
total = min(total, solve((1 << n) - 1, i) + f(i));Виводимо бажану мінімальну довжину шляху.
printf("%.6lf\n", total);