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

Автор JKeeJ1e30, 13 лет назад, По-русски

Я не знаю, баяном ли является то, что я придумал, но единственный алгоритм, который я нашел на емаксе, решает задачу при помощи Флойда-Уоршелла, который, как известно, работает за O(n3). Ниже приводится описание алгоритма, который работает за O(n*m).


Итак, суть задачи. У нас есть произвольный ориентированный граф с n вершинами и m ребрами, нужно найти все пары вершин, такие что путь между ними бесконечно мал.

Шаг 1. Из данного графа получим конденсацию. 
 
Следующее утверждение: если в компоненте сильной связности есть цикл отрицательного веса, то между любой парой вершин из этой компоненты сильной связности есть бесконечно малый путь.

В самом деле, доберемся из начальной вершины до цикла - а это мы можем сделать, так как у нас компонента сильной связности, - пройдем по циклу любое произвольное число раз и перейдем в нашу вторую вершину - опять же, мы сможем это сделать в силу КСС.

Шаг 2. Итак, запустим Форда-Беллмана в каждой КСС. Он найдет нам отрицательный цикл, если он есть. 

Шаг 3, а. Произведем серию поисков в ширину на 0-1 графе из каждой вершины полученной конденсации.

Давайте будем называть "черной" ту вершину конденсации, у которой внутри нее есть цикл отрицательного веса. 

Утверждение: если в первоначальном графе между двумя вершинами есть путь, проходящий хотя бы через одну "черную" компоненту связности(включая компоненты связности этих двух вершин), то между двумя вершинами существует сколь угодно малый по величине путь.

Действительно, зайдем в эту компоненту связности, дойдем до цикла, проитерируемся по циклу любое произвольное число раз, и потом выйдем из цикла и дойдем до второй вершины.

Теперь как мы будем осуществлять поиск в ширину. Если первая вершина была черная, то перекрасим всех ее сыновей в черный цвет, и так далее. Иначе сначала переберем всех черных сыновей, и от них запустимся как раньше - покрасим всех сыновей черного сына в черный цвет. Очевидно, что все вершины, которые стали черными - между ними и начальной вершиной лежит вершина, в которой есть цикл отрицательного веса.

Шаг 3, б. Результаты обходов сохраним в матрице смежности.

Шаг 4. Для каждой пары вершин за O(1) найдем, в какой КСС они лежат, и по построенной матрице определим, лежит ли между ними цикл отрицательного веса, так же за O(1). 

Асимптотическая сложность: по шагам: 1)O(n*logn) 2)O(n*m) 3)O(n1*m1); так как n1<=n и m1<=m, получим O(n*m). 4)O(n2). 
То есть суммарно не более чем O(n*m)

Сложность по памяти: 0) начальный граф - O(m + n) 1) для хранения конденсированного графа O(m + n) и для каждой вершины ссылка на номер ее КСС - O(n)  2-3) под матрицу O(n1*n1) = O(n*n) 4) 0. 
Итого O(n2 + 2*m + 3 * n)

Вопросы от автора:
1) это баян?
2) Видите ли вы какие-нибудь оптимизации? Особенно это касается оптимизации по памяти.

Реализация алгоритма - http://pastie.org/2774882
  • Проголосовать: нравится
  • +26
  • Проголосовать: не нравится

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


  • 13 лет назад, # ^ |
    Rev. 5   Проголосовать: нравится +3 Проголосовать: не нравится

    Можно вместо пункта 3 пустить динамику вида "множество достижимых компонент" вверх по конденсации (объединение по потомкам), и параллельно "множество компонент - ответов" (это либо объединение по потомкам, либо, если сама компонента черная, все достижимые). Будет то же, только битовое. И O(V^2) памяти (тоже битовой при желании).

13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Не оно ли?
  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    В этой задаче я пропихивал Флойда.

    Как я понял, он спрашивает, не существовало ли такого алгоритма раньше.
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Ну, или же алгоритма с такой же/более хорошей ассимптотической сложностью.
  • 13 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится
    Тут куб заходит
13 лет назад, # |
  Проголосовать: нравится -8 Проголосовать: не нравится
Утверждается что шаг с разбиением на КСС можно пропустить.
Задача "посчитать за O(nm) расстояния от source до всех или сказать, что до i-й он бесконечно мал" боян древнейший. В последний раз видел на Andrew Stankevich Contest # N. Для этой задачи алгоритм отличается только добавлением фиктивной вершины из которой есть рёбра 0 во все остальные.

  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    А тут алгоритм для всех вершин
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Если я не ошибаюсь, то алгоритм, о котором написал Narg, тоже для всех вершин.
      • 13 лет назад, # ^ |
        Rev. 3   Проголосовать: нравится -6 Проголосовать: не нравится

        То, что предлагает Narg работает за n*m, но находит только одну такую вершину. Если же добавлять ребра веса 0 из добавленной вершины, то если я правильно понял это вообще WA на тесте -1 получается.

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

          Значит, плохо понял.
          Объясняю подробнее:
          1. Добавим фиктивную вершину = инициализируем расстояния 0.
          2. Запускаем ФБ, запоминая предка каждой вершины.
          3. После |V|-й итерации найдём все циклы в графе из рёбер вида (вершина, её предок). Для каждой компоненты верно, что если в ней есть отрицательный цикл, то какой-то из них найден.
          4. Для каждой вершины v хотим сказать до каких вершин есть сколь угодно малый путь (это, кстати, не тоже самое, что "путь между ними бесконечно мал". Путь подразумевается кратчайший, но его не существует.). Для этого: найдём (обходом из v) вершины циклов, достижимых из неё. Запустим заново обход c них и будем выводить все посещенные вершины как ответ для v.

          Итого: время O(|V| * |E|), память O(|V| + |E|), нет явного построения КСС.

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

            Как же мы после n-й итерации найдем все циклы?  По-моему, это неверно. Вот тест:

            6 7
            1 2 -2
            2 3 -2
            3 1 -2
            6 4 -1
            4 5 -1
            5 6 -1
            1 4 -100

            Он будет во второй КСС на каждом ходу апдейтить ответ из того цикла, который начинается в первой компоненте. Если он будет делать именно это, то цикла во второй компоненте мы просто не найдем.
            • 13 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Для наглядности распишу, каких предков он будет запоминать:

              1 шаг: 
              d[1] = 0, предок - фиктивная вершина;
              d[2] = 0, предок - фиктивная вершина;
              d[3] = 0, предок - фиктивная вершина;
              d[4] = 0, предок - фиктивная вершина;
              d[5] = 0, предок - фиктивная вершина;
              d[6] = 0, предок - фиктивная вершина;

              2 шаг: 
              d[1] = -2, предок - 3;
              d[2] = -2, предок - 1;
              d[3] = -2, предок - 2;
              d[4] = -100, предок - 1;
              d[5] = -1, предок - 4;
              d[6] = -1, предок - 5;

              3 шаг: 
              d[1] = -4, предок - 3;
              d[2] = -4, предок - 1;
              d[3] = -4, предок - 2;
              d[4] = -102, предок - 1;
              d[5] = -101, предок - 4;
              d[6] = -101, предок - 5;

              4 шаг: 
              d[1] = -6, предок - 3;
              d[2] = -6, предок - 1;
              d[3] = -6, предок - 2;
              d[4] = -104, предок - 1;
              d[5] = -103, предок - 4;
              d[6] = -103, предок - 5;

              5 шаг: 
              d[1] = -8, предок - 3;
              d[2] = -8, предок - 1;
              d[3] = -8, предок - 2;
              d[4] = -106, предок - 1;
              d[5] = -105, предок - 4;
              d[6] = -105, предок - 5;

              Ну и так далее. Для четвертой вершины он все время будет апдейтить из первой компоненты. Выходит, что цикла отрицательного веса во второй компоненте(4-5-6-4) мы просто не найдем.
            • 13 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Честно говоря, лень читать комент ниже и разбирать текущий пример выше, но вообще 2-я или 3-я правка навели меня на мысль, что я чушь прогнал, конечно, и во второй компоненте так можно затереть все пометки предков релакцией сильно мелких рёбер из первой. Тогда цикл не найдётся и непонятно как это исправить без построения КСС.
              Но квадратной памяти всё равно не нужно. Ничего кроме разбиения на компоненты и пометок в какой есть цикл отрицательного веса не нужно, чтобы просто за n обходов вывести все пары вершин.
              • 13 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                Вы имели в виду что-то типа этого?

                P.S. со временем беда. Почему-то на тесте n = 5000, m = 20000  на своем компе работает 24 секунды. При этом на тесте n = 5000, m = 5000 летает за нулевое время. Может, кто посмотрит, может, я безрукий просто?
                • 13 лет назад, # ^ |
                    Проголосовать: нравится 0 Проголосовать: не нравится
                  Компилятор / IDE?
                • 13 лет назад, # ^ |
                    Проголосовать: нравится 0 Проголосовать: не нравится
                  Все, полностью разобрался, каким-то образом случайные тесты на графе 5000 20000 генерируются таким образом, что компонента связности получается одна на весь граф, да к тому же отрицательный цикл внутри. Программа теперь работает меньше 13 сек на моем динозавре, если я не вывожу а просто храню вершины, на сервере же КФ - в два - два с половиной раза быстрее. 
13 лет назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится
Ну почем в России всегда все пишут с удвоенными буквами, особенно Ассимптотическая сложность?
13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Так что? Баян или нет? 
  • 13 лет назад, # ^ |
      Проголосовать: нравится +13 Проголосовать: не нравится

    Ну, видимо - не боян, но теперь стал им :)

    Проблема этой задачи, наверное, в том, что не так-то просто отделить быстрый N^3 Флойда от NM достаточно навороченного алгоритма.

    • 13 лет назад, # ^ |
        Проголосовать: нравится -10 Проголосовать: не нравится
      Он не очень навороченный, и, кстати, там в очень редких случаях будет оценка n*m. Ну вот смотри, при запуске Форда-Беллмана в каждой КСС он нам сделает фактически n1*m1+n2*m2+n3*m3+... операций. Ясно, что максимум приблизительно достигается, когда одна-две компоненты. А поиск в ширину, наоборот, максимум-когда компонент достаточно много. Так что оценка m*n достигается достаточно редко, по-видимому.
      • 13 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Да. Оценка достигается редко, но дело еще в том, что константа получается все-таки не очень большая, так как если он в одном месте будет работать O(n*m) - в другом он будет работать за O(побырику)