Задание №18 — Задачи на динамику для таблиц
Квадрат разлинован на N × N клеток (1 < N < 30). Исполнитель Робот может перемещаться по клеткам, выполняя за одно перемещение одну из двух команд: вправо или вниз. По команде вправо Робот перемещается в соседнюю правую клетку, по команде вниз –в соседнюю нижнюю. Квадрат ограничен внешними стенами. Между соседними клетками квадрата также могут быть внутренние стены. Сквозь стену Робот пройти не может. Перед каждым запуском Робота в каждой клетке квадрата лежит монета достоинством от 1 до 100. Посетив клетку, Робот забираетмонету с собой; это также относится к начальной и конечной клеткам маршрута Робота. В «угловых» клетках поля – тех, которые справа и снизу ограничены стенами, Робот не может продолжать движение, поэтому накопленная сумма считается итоговой. Таких конечных клеток на поле может быть несколько, включая правую нижнюю клетку поля. При разных запусках итоговые накопленные суммы могут различаться.
Определите максимальную и минимальную денежные суммы среди всех возможных итоговых сумм, которые может собрать Робот, пройдя из левой верхней клетки в конечную клетку маршрута. В ответе укажите два числа – сначала максимальную сумму, затем минимальную. Исходные данные представляют собой электронную таблицу размером N × N, каждая ячейка которой соответствует клетке квадрата. Внутренние и внешние стены обозначены утолщёнными линиями.
Пример вводных данных:
| 1 | 8 | 8 | 4 |
| 10 | 1 | 1 | 3 |
| 1 | 3 | 12 | 2 |
| 2 | 3 | 5 | 6 |
Для указанных входных данных ответом должна быть пара чисел:
| 58 | 32 |
Правильный ответ
2255 820
Пояснение
Решение:
Робот ходит только вправо и вниз, поэтому в любую клетку он приходит либо сверху, либо слева и ни одной клетки не проходит дважды. Значит, работает динамическое программирование: для каждой клетки достаточно хранить максимальную и минимальную суммы, которые можно накопить, дойдя до неё из левой верхней клетки.
Пусть — номинал монеты в клетке, и — максимальная и минимальная накопленные суммы. Тогда и , причём соседняя клетка учитывается только тогда, когда между ней и текущей нет стены. Для левой верхней клетки .
Отличие этой задачи в том, что маршрут заканчивается не обязательно в правой нижней клетке: конечной является любая клетка, у которой и справа, и снизу стена (или край поля). Поэтому нужно перебрать все такие клетки и взять наибольшую и наименьшую из накопленных в них сумм.
В приложенном файле поле имеет размер . Вертикальные стены (нельзя идти вправо) стоят:
- между 4-м и 5-м столбцами — в строках 12–16;
- между 7-м и 8-м столбцами — в строках 4–19;
- между 12-м и 13-м столбцами — в строках 2–11;
- между 17-м и 18-м столбцами — в строках 11–18;
- между 19-м и 20-м столбцами — в строках 5–10.
Горизонтальные стены (нельзя идти вниз) стоят между 4-й и 5-й строками в столбцах 16–19, между 11-й и 12-й строками в столбцах 10–12, между 16-й и 17-й строками в столбцах 2–4, между 18-й и 19-й строками в столбцах 13–17 и между 19-й и 20-й строками в столбцах 5–7.
Скопируем содержимое таблицы в текстовый файл 18.txt (числа разделены пробелами или знаками табуляции) и обработаем его программой.
f = open('18.txt')
a = [[int(x) for x in s.split()] for s in f]
N = len(a)
# стена снизу от клетки (i, j); строки и столбцы нумеруются с нуля
def wall_down(i, j):
return ((i == 3 and 15 <= j <= 18) or (i == 10 and 9 <= j <= 11) or
(i == 15 and 1 <= j <= 3) or (i == 17 and 12 <= j <= 16) or
(i == 18 and 4 <= j <= 6))
# стена справа от клетки (i, j)
def wall_right(i, j):
return ((j == 11 and 1 <= i <= 10) or (j == 6 and 3 <= i <= 18) or
(j == 18 and 4 <= i <= 9) or (j == 16 and 10 <= i <= 17) or
(j == 3 and 11 <= i <= 15))
MX = [[0] * N for i in range(N)] # максимум, накопленный к клетке
MN = [[0] * N for i in range(N)] # минимум
for i in range(N):
for j in range(N):
if i == 0 and j == 0:
MX[i][j] = MN[i][j] = a[0][0]
continue
v = []
if i > 0 and not wall_down(i - 1, j):
v.append((MX[i - 1][j], MN[i - 1][j]))
if j > 0 and not wall_right(i, j - 1):
v.append((MX[i][j - 1], MN[i][j - 1]))
MX[i][j] = max(x for x, y in v) + a[i][j]
MN[i][j] = min(y for x, y in v) + a[i][j]
best_max = 0
best_min = 10 ** 9
for i in range(N):
for j in range(N):
# клетка конечная, если справа и снизу от неё стена (или край поля)
if (j == N - 1 or wall_right(i, j)) and (i == N - 1 or wall_down(i, j)):
best_max = max(best_max, MX[i][j])
best_min = min(best_min, MN[i][j])
print(best_max, best_min)
Конечных клеток оказывается пять; накопленные в них суммы такие:
| Конечная клетка (строка, столбец) | Максимум | Минимум |
|---|---|---|
| (11, 12) | 1431 | 870 |
| (16, 4) | 1173 | 820 |
| (18, 17) | 2252 | 1369 |
| (19, 7) | 1566 | 947 |
| (20, 20) | 2255 | 1577 |
Наибольшая из всех итоговых сумм — 2255 (правая нижняя клетка), наименьшая — 820 (клетка в 16-й строке и 4-м столбце).
Ответ: 2255 820