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

Автор palliative, 4 года назад, По-русски

Мне показалось забавным попытаться соединить два популярных алгоритма в один

Рассмотрим следующую задачу.
Дан связный неорграф из $$$n$$$ вершин без петель и кратных рёбер. Также задан шаблон обхода DFS — $$$a_1, a_2, \ldots, a_n$$$, который означает, что $$$i$$$-ой по счёту вершиной нужно посетить $$$a_i$$$, если $$$a_i \neq 0$$$, и любую другую иначе.
Требуется найти удовлетворяющий шаблону обход, или сообщить что такого нет.

Пример

Идея решения заключается в том, чтобы на графе всевозможных состояний обхода DFS запустить BFS.
Для одного состояния будем хранить три массива:

  • used — была ли посещена вершина
  • stack — текущий стек вершин
  • path — общий проделанный путь
Класс состояния обхода (C++)

Заведём очередь q (как и полагается в BFS). Так как мы, скорее всего, не знаем с какой вершины нужно начать обход, создадим фиктивную вершину и соединим её со всеми остальными. Изначально в q положим состояние с этой вершиной.

Пока q не пуста, и ещё не нашли ответ, извлекаем оттуда очередное состояние cur. Пусть last — последняя вершина в стеке, то есть та вершина, в которой сейчас остановился cur. Из непосещённых соседей last выбираем те, пройдя в которые заданный порядок входов в вершины не нарушится, и добавляем нужные состояния в очередь. Если же все соседи посещены, то добавляем в очередь состояние cur, но с извлечённой из стека последней вершиной (что соответствует обратному ходу рекурсии в DFS).
Ответом будет состояние с длиной пути $$$n$$$.

Код получился несколько громоздким (большей частью из-за ООП, который был выбран для понятности), но довольно простым.

Решение (C++)

Асимптотика
Сначала найдём $$$V, E$$$ — количество вершин и ребёр у нашего графа состояний.
Для простоты будем считать, что изначально на вход был дан полный граф и шаблон состоит полностью из нулей (то есть нет ограничений на порядок вершин). В таком случае состояние характеризуется двумя числами — длиной пройденного пути и самим путём.
При длине пути $$$0$$$ есть ровно одно состояние (состоящее из фиктивной вершины). При длине $$$= 1$$$ есть $$$n$$$ состояний и т.д. — при длине $$$i \le n$$$ есть $$$(n)_i = \frac{n!}{(n - i)!} = n \cdot (n - 1) \cdot \ldots \cdot (n - i + 1)$$$ состояний.
Тогда всего вершин $$$V = \sum_{i = 0}^n (n)_i$$$.
Из каждого состояния можно перейти в не более чем $$$n$$$ других, поэтому $$$E \le nV$$$.
BFS на графе из $$$V$$$ вершин и $$$E$$$ рёбер работает за $$$O(V + E)$$$. Также учтём, что одно состояние обрабатывается за $$$O(n)$$$ из-за копирования массивов. Итого алгоритм работает за $$$O(n^2V) = O\Big(n^2 \cdot \sum_{i = 0}^n (n)_i\Big) = O\big(n^2(n + 1)!\big)$$$.

Если учитывать ограничения на времена входа в вершины, можно получить асимптотику получше.

Более сложная оценка времени работы

Warning!
Алгоритм не претендует на практическую полезность.
Любое совпадение считать случайным :)

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

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

эту задачу можно сделать за n^2 * 2^n через дп по маскам. Скажем dp[v][mask] — можно ли из состояния (v, mask) сделать нужный путь, где v — стартовая вершина, а в mask стоят единичные биты на месте уже пройденных вершин (и вершины v тоже)

Тогда номер хода получается из количества единичных битов (__builtin_popcount(mask) находит это за O(1)). Раз dfs обход, то в одну вершину дважды нельзя, т.е. маска как раз может быть использована как used. База динамики, конечно, когда все биты единичные, dp[v][все единицы] = 1. Переходы же dp[v][mask] -> max(dp[to][mask | (1 << to)]), где to получается по номеру хода [ai != 0] (либо все соседи v [ai == 0]). Восстановление пути — это просто восстановление оптимального пути в динамике, классическая задача.

думаю, что для удобства можно даже сделать dp[v][mask] = to (куда пойти, чтобы был нужный путь, либо -1). Есть так же мысль, что за n * 2^n можем, но надо ухитрится как в поиске гамильтонова пути (там есть вариант за n * 2^n), но может это и нельзя.