Рассмотрим какую-нибудь задачу на перебор подмножеств, которую мы умеем решать за , где — какой-то полином от размера задачи . Метод meet-in-the-middle (дословно, «встреча в середине») позволяет соптимизировать перебор до в большом классе таких задач.
В качестве конкретного примера, рассмотрим задачу о рюкзаке — нужно выбрать подмножество с суммарным весом :
bool find_subset(int *a, int n, int w)
for (int mask = 0; mask < (1 << n); mask++) {
int s = 0;
for (int i = 0; i < n; i++)
if (mask >> i & 1)
s += a[i];
if (s == w)
return true;
}
return false;
}
Здесь мы перебираем все подмножества и каждое проверяем за , что дает асимптотику .
В теории можно избавиться от проверки за , если перебирать маску рекурсивно и поддерживать текущую сумму на префиксе, возможно добавляя во время спуска только один элемент. Однако мы погонимся за более мощной оптимизацией.
#Решение
Разделим массив на две части. Заметим, что искомое подмножество имеет какое-то количество элементов из левой половины и какое-то количество элементов из правой (возможно, нулевое). Попытаемся посчитать все суммы слева и справа по отдельности и найти пару, дающую нужную общую сумму.
Сначала посчитаем суммы для всех подмножеств среди первых элементов и положим в хеш-таблицу:
unordered_set<int> t;
int l = n / 2;
for (int mask = 0; mask < (1 << l); mask++) {
int s = 0;
for (int i = 0; i < n; i++)
if (mask >> i & 1)
s += a[i];
t.insert(s);
}
Затем переберем все суммы среди оставшихся элементов и для каждой попытаемся найти подходящую половину (с суммой ) через предподсчитанную хеш-таблицу:
int r = n - l;
for (int mask = 0; mask < (1 << r); mask++) {
int s = 0;
for (int i = 0; i < r; i++)
if (mask >> i & 1)
s += a[l + i];
if (t.count(w - s))
return true;
}
Обе фазы (а значит и сам алгоритм) работают за : мы перебираем подмножеств и для каждого за считаем сумму и делаем запрос добавления / проверки наличия в хеш-таблицу за .
Заметим, что оба перебора всё ещё можно так же соптимизировать в раз через трюк с рекурсией.
#Другие примеры
Задача. Найти количество чисел до с суммой цифр .
Разделим число на левую часть (старшие 7 разрядов) и правую (младшие 7 разрядов). Для всех чисел левой части предподсчитаем, сколько из них имеют сумму и запишем это в массив предподсчёта (его размер будет ). Затем переберем правую часть, и тогда для «левой» суммы нам нужно найти количество правых частей с суммой , для чего мы просто за обращаемся к предподсчитанному массиву.
На больших ограничениях эта задача решается через динамику по цифрам.
Задача. Дан граф из вершин. Нужно найти количество клик — подграфов, в котором все вершины связаны со всеми.

Сначала научимся решать задачу полным перебором. Пусть у нас есть матрица смежности графа. Как быстро проверить, что подмножество вершин является кликой?
За можно пройтись по всем парам включенных вершин и для каждой проверить, есть ли единичка в матрице смежности. Проверку можно соптимизировать до , посчитав маску , равную побитовому «И» строчек матрицы смежности, соответствующих вершинам . Теперь, если является подмножеством , то есть
то подграф является кликой: для всех его вершин есть ребро из всех других.
Воспользуемся этим трюком для слияния ответов в meet-in-the-middle. Разделим граф на две части, найдем для левой все клики и пометим их маски единицами в специальном массиве is_clique[mask]
размера .
Теперь будем перебирать подграфы второй половины, и для каждой клики нам нужно найти количество клик левой половины, являющихся подграфами пересечения списков смежности для правой половины ( из проверки выше).
Чтобы сделать это быстро, предподсчитаем поверх массива is_clique
динамику «как много подмасок данной маски являются кликами». Эту динамику можно посчитать за , если для каждой маски рассмотреть два варианта — когда первая вершина включена в клику и когда не включена:
Итоговая асимптотика алгоритма будет .
Задача. Неявно задан очень большой невзвешенный граф, в котором у каждой вершины не более переходов. Требуется найти расстояние от до , если известно, что оно не превышает .
Запустим обход в ширину одновременно из обеих вершин, записывая рядом с вершинами расстояния от или найденные соответствующим обходом. Когда обход с одной стороны зайдет в вершину, посещенную другим обходом, то восстанавливаем путь размера и завершаемся.
Так как расстояние не превосходит , то алгоритм гарантированно завершится за «волн». Так как каждая вершина связана с не более другими, и в «родителя» алгоритм не заходит, на следующей волне будет не более чем в раз больше вершин, чем на предыдущей.
Значит суммарно будет посещено вершин, чему и будет равна асимптотика алгоритма.