Блог пользователя dmkz

Автор dmkz, история, 7 лет назад, По-русски

Привет, codeforces! В рамках проекта по подготовке к олимпиадам по программированию в качестве задания была выдана эта задача на двумерное динамическое программирование.

Кратко суть задачи: дана матрица размера n2, где n ≤ 200 , в которой есть нулевые и ненулевые элементы. Расстояние на матрице между ячейками (x1, y1) и (x2, y2) определяется как dist = |x1 - x2| + |y1 - y2|. Каждый нулевой элемент нужно заменить ближайшим к нему ненулевым элементом, а если подходящих элементов несколько, то оставить без изменений.

Я написал решение на C++, которое получило вердикт Accepted, и решил ради эксперимента перенести его на python3, чтобы попрактиковаться с новым языком программирования. Вот что из этого вышло. Действий получается порядка O(n2), но с большой константой. При n = 200 должно пройти, но не проходит — вердикт TLE. Можно ли как-нибудь ускорить решение на python3, заменив ввод-вывод более быстрым (уже вроде как заменил, но может можно еще быстрее), или применив какие-нибудь другие хитрости, так, чтобы оно получило вердикт Accepted?

Кратко суть решения:

  1. Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≥ x *  и y ≥ y * .
  2. Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≤ x *  и y ≥ y * .
  3. Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≥ x *  и y ≤ y * .
  4. Поиск ближайшего элемента для (x, y) среди всех элементов (x * , y * ) таких что, x ≤ x *  и y ≤ y * .
  5. Объединение ответов

Для каждого прохода 1-4 используем метод ДП, комбинируя ответы для соседних элементов, например, в 1 пункте: answer[x][y] = combine(answer[x - 1][y], answer[x][y - 1]) + 1

  • Проголосовать: нравится
  • 0
  • Проголосовать: не нравится

»
7 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

Вроде можно запустить один bfs сразу из всех ненулевых вершин. Тогда мы по каждому ребру пройдем только однажды.

По коду: 1. Конкатенация строчек правда быстрая? Вряд ли она там не за квадрат. Можно сложить в массив и сделать join. (Этот кусок кода рекомендую отдельно потестить)
2. Мне не нравятся копирование этих объектов(хотя я не особо шарю, насколько это хорошо в питоне работает), кажется что условия вида "если надо обновить, обновляем то, что нужно, иначе вообще не трогаем" будут получше работать.

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Конкатенация к одной строке другой работает, видимо, за линию, потому что этот код:

    s = "";
    for x in range(1000000): 
        s += str(x) + " ";
    print(len(s))
    

    Отрабатывает почти мгновенно

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

ну и кстати если написать [int(x) for x in s.split()] понятно где, то можно выкинуть половину инпута

»
7 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Ну и если уж собрались ускорять, сколько реально-то работает на макс.тесте (локально, хотя бы)

  • »
    »
    7 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    Провел замены, которые Вы предложили, обновил исходник. На полностью нулевой матрице работает чуть меньше секунды выдает система через hwclock, и 0.248s выдает профайлер

    • »
      »
      »
      7 лет назад, # ^ |
      Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

      Ну один бфс вроде я пока не увидел(то есть сложить не нули в очередь, пойти от них, также насчитывать количества, но для каждого будет только один best). Я не сильно уверен, что поможет, но на тесте со всеми нулями должно быть мгновенно:)

      • »
        »
        »
        »
        7 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        bfs на плюсах написан и сдан еще раньше. На питон пришла в голову мысль попробовать перенести только вариант с динамикой.

»
7 лет назад, # |
  Проголосовать: нравится +13 Проголосовать: не нравится

В таком случае лучше сразу профайлинг запустить, на питоне это просто: python -m cProfile code.py

Видно, что copy медленный. Исправляем:

def copy(a):
    return a.copy()

class Record:
    ...
    def copy(self):
        return Record(**self.__dict__)

Получается в два раза быстрее на нулевой матрице. Из очевидного ещё, вместо класса наверное просто list быстрее будет..

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Заменил и обновил исходник. Локально профайлер выдал "359305 function calls in 0.248 seconds" на нулевой матрице, на сервере снова TLE, но уже на следующем тесте (решение прошло дальше, чем до этого).

    • »
      »
      »
      7 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Можно ещё попробовать явно в копии передать поля

»
7 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

Вы в inc никогда не пользуетесь тем, что исходный объект изменился (везде берете старый). Если не изменять исходный объект, то вроде можно избавиться от кучи копий. У меня получилось 0.84 -> 0.68 (но мог и сломать)

Дальше -- объединяем два инта в тупл, чтобы не копировать их поотдельности при операции inc https://pastebin.com/grfdxGuQ (еще процентов 10)

  • »
    »
    7 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Спасибо, реально стало быстрее, но все равно недостаточно быстро. Видимо, на питоне вариант с динамикой просто не сдать в отведенное время на этом сервере