Привет, codeforces! В рамках проекта по подготовке к олимпиадам по программированию в качестве задания была выдана эта задача на двумерное динамическое программирование.
Кратко суть задачи: дана матрица размера n2, где n ≤ 200 , в которой есть нулевые и ненулевые элементы. Расстояние на матрице между ячейками (x1, y1) и (x2, y2) определяется как dist = |x1 - x2| + |y1 - y2|. Каждый нулевой элемент нужно заменить ближайшим к нему ненулевым элементом, а если подходящих элементов несколько, то оставить без изменений.
Я написал решение на C++, которое получило вердикт Accepted, и решил ради эксперимента перенести его на python3, чтобы попрактиковаться с новым языком программирования. Вот что из этого вышло. Действий получается порядка O(n2), но с большой константой. При n = 200 должно пройти, но не проходит — вердикт TLE. Можно ли как-нибудь ускорить решение на python3, заменив ввод-вывод более быстрым (уже вроде как заменил, но может можно еще быстрее), или применив какие-нибудь другие хитрости, так, чтобы оно получило вердикт Accepted?
Кратко суть решения:
- Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≥ x * и y ≥ y * .
- Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≤ x * и y ≥ y * .
- Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≥ x * и y ≤ y * .
- Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≤ x * и y ≤ y * .
- Объединение ответов
Для каждого прохода 1-4 используем метод ДП, комбинируя ответы для соседних элементов, например, в 1 пункте: answer[x][y] = combine(answer[x - 1][y], answer[x][y - 1]) + 1
Вроде можно запустить один bfs сразу из всех ненулевых вершин. Тогда мы по каждому ребру пройдем только однажды.
По коду: 1. Конкатенация строчек правда быстрая? Вряд ли она там не за квадрат. Можно сложить в массив и сделать join. (Этот кусок кода рекомендую отдельно потестить)
2. Мне не нравятся копирование этих объектов(хотя я не особо шарю, насколько это хорошо в питоне работает), кажется что условия вида "если надо обновить, обновляем то, что нужно, иначе вообще не трогаем" будут получше работать.
Конкатенация к одной строке другой работает, видимо, за линию, потому что этот код:
Отрабатывает почти мгновенно
ну и кстати если написать
[int(x) for x in s.split()]
понятно где, то можно выкинуть половину инпутаНу и если уж собрались ускорять, сколько реально-то работает на макс.тесте (локально, хотя бы)
Провел замены, которые Вы предложили, обновил исходник. На полностью нулевой матрице работает чуть меньше секунды выдает система через hwclock, и 0.248s выдает профайлер
Ну один бфс вроде я пока не увидел(то есть сложить не нули в очередь, пойти от них, также насчитывать количества, но для каждого будет только один best). Я не сильно уверен, что поможет, но на тесте со всеми нулями должно быть мгновенно:)
bfs на плюсах написан и сдан еще раньше. На питон пришла в голову мысль попробовать перенести только вариант с динамикой.
В таком случае лучше сразу профайлинг запустить, на питоне это просто:
python -m cProfile code.py
Видно, что copy медленный. Исправляем:
Получается в два раза быстрее на нулевой матрице. Из очевидного ещё, вместо класса наверное просто list быстрее будет..
Заменил и обновил исходник. Локально профайлер выдал "359305 function calls in 0.248 seconds" на нулевой матрице, на сервере снова TLE, но уже на следующем тесте (решение прошло дальше, чем до этого).
Можно ещё попробовать явно в копии передать поля
Вы в inc никогда не пользуетесь тем, что исходный объект изменился (везде берете старый). Если не изменять исходный объект, то вроде можно избавиться от кучи копий. У меня получилось 0.84 -> 0.68 (но мог и сломать)
Дальше -- объединяем два инта в тупл, чтобы не копировать их поотдельности при операции inc https://pastebin.com/grfdxGuQ (еще процентов 10)
Спасибо, реально стало быстрее, но все равно недостаточно быстро. Видимо, на питоне вариант с динамикой просто не сдать в отведенное время на этом сервере
¯\_(ツ)_/¯