Динамика по подмножествам. Часть 2
Оглавление
Запись занятия
Проверка наличия Гамильтонова пути
Дан граф на $n$ $(n \leq 20)$ вершинах. Необходимо проверить, есть ли в этом графе путь, проходящий по всем вершинам ровно один раз.
На прошлой лекции мы разобрали как за время $O(2^{n} n^2)$ можно найти кратчайший гамильтонов путь. Оказывается, если ослабить задачу и просто проверять наличие Гамильтонова пути, то можно придумать решение за $O(2^{n} n)$.
Напоминание решения за $O(2^{n} n^2)$
Пусть $dp[mask][v]$ - будет истиной, если существует путь, проходящий по вершинам из $mask$ и оканчивающийся в вершине $v$. Пересчёт динамики тоже простой:
\[dp[mask][v] \to dp[mask\ |\ 2^u][u], \quad if\ (v, u) \in E\]То есть если мы могли попасть в состояние $(mask, v)$ и есть ребро $(v, u)$, то сможем попасть в состояние $(mask\ |\ 2^u, u)$, пройдя по этому ребру.
Решение за $O(2^{n} n)$
А не кажется ли вам, что хранить в каждом состоянии всего лишь один бит информации как в прошлом пункте - слишком жирно? Вот и мне кажется, что на этом можно попробовать сыграть!
Итак, пусть $dp[mask] = mask_v$ - это динамика, в которой хранится маска $mask_v$, в которой в $v$-м бите $1$, если существует путь, проходящий по вершинам $mask$ и оканчивающийся в вершине $v$. То есть всё что мы заменили - это сжали наше состояние из прошлой динамики до маски. Однако за счёт битовых операций нам получится ускорить решение до $O(2^{n} n)$.
Пусть мы хотим проверить, можем ли мы пройти по пути, состоящем из маски $mask$, оканчивающейся в вершине $u$. Тогда меня интересует маска $mask \oplus 2^{u}$ и любая $v$, из которой есть ребро в $u$, чтобы оказаться в состоянии $(mask, u)$. То есть мне нужно проверить, а есть ли $v$, в которой ОДНОВРЕМЕННО заканчивается путь по маске $mask \oplus 2^{v}$ и она связна с $u$. А это не что иное как битовое И состояния динамики и строки из матрицы смежности: $dp[mask \oplus 2^{u}]\ \&\ adj[u]$, где $adj[u]$ - маска вершин, с которыми соединена $u$.
То есть динамика считается следующим образом:
1
2
3
4
5
6
7
8
9
10
11
for (int i = 0; i < n; ++i) { // база динамики
dp[1 << n] = 1 << n;
}
for (int mask = 0; mask < (1 << n); ++mask) {
for (int u = 0; u < n; ++u) {
if (((mask >> u) & 1) && (dp[mask ^ (1 << u)] & adj[u])) {
dp[mask] |= 1 << u;
}
}
}
Перебор всех подмасок данной маски
Иногда это в задачах просто необходимо пройтись по всем подмаскам данной маски, чтобы пройтись по всем переходам динамики или вовсе осуществить полный перебор. Однако, если перебирать все пары масок и каждый раз проверять является ли вторая маска подмаской первой, то получится алгоритм за $4^n$, что катастрофически много.
Оказывается, что если перебирать только подмаски, то всего пар $(mask, submask)$ всего $3^n$. Комбинаторное доказательство этого факта оставим вам в качестве упражнения.
А код, который перебирает все подмаски выглядит следующим образом:
1
2
3
4
5
for (int mask = 0; mask < (1 << n); ++mask) {
for (int submask = mask; submask > 0; submask= (submask - 1) & mask) {
// ...
}
}
Объясним простоту и элегантность полученного цикла:
Пусть у нас есть текущая подмаска $submask$, и мы хотим перейти к следующей подмаске (по убыванию). Отнимем от маски $submask$ единицу, тем самым мы снимем самый правый единичный бит, а все биты правее него поставятся в $1$. Затем удалим все “лишние” единичные биты, которые не входят в маску $mask$, и потому не могут входить в подмаску. Удаление осуществляется битовой операцией $\& mask$. В результате мы “обрежем” маску $ssubmask - 1$ до того наибольшего значения, которое она может принять, т.е. до следующей подмаски после $submask$ в порядке убывания.
Таким образом, этот алгоритм генерирует все подмаски данной маски в порядке строгого убывания, затрачивая на каждый переход по две элементарные операции.
Особо рассмотрим момент, когда $submask = 0$. После выполнения $submask - 1$ мы получим маску, в которой все биты включены (битовое представление числа $-1$), и после удаления лишних битов операцией $(submask - 1) \& m$ получится не что иное, как маска $mask$. Поэтому с маской $submask = 0$ следует быть осторожным - если вовремя не остановиться на нулевой маске, то алгоритм может войти в бесконечный цикл.
Бонус: научитесь перебирать все надмаски данной маски.
Решение
1
2
3
for (int mask = need; mask < (1 << n); mask = (mask + 1) | need) {
// some code
}
Хроматическое число графа
Необходимо покрасить граф из $n$ ($n \leq 17$) вершин в минимальное количество цветов так, чтобы никакие две соседние по ребру вершины не были одного цвета.
Про магазины
Есть $n$ ($n \leq 100$) магазинов и $m$ ($m \leq 16$) товаров. Нужно купить товар каждого вида за минимальную цену, при этом известны $c_{i,j}$ - стоимость $j$-го товара в магазине $i$ и $d_i$ - стоимость доехать до магазина $i$.
Наибольшая общая надстрока
Есть $n$ ($n \leq 100$) строк $s_i$ $( s_i \leq 50)$. Необходимо найти наименьшую общую настроку строк, то есть такую строку и среди таких - лексикографически минимальную.
Ссылки
- Перебор подмасок
- Какая-то древняя задача
- Апроксимации (приближенные решения) для задачи наибольшей общей надстроки