Meet-in-the-middle — различия между версиями
Shersh (обсуждение | вклад) (переписано определение) |
Shersh (обсуждение | вклад) м |
||
| Строка 6: | Строка 6: | ||
== Задача о нахождение четырех чисел с суммой равной нулю == | == Задача о нахождение четырех чисел с суммой равной нулю == | ||
| − | Дан массив целых чисел <tex>{A}</tex>. Требуется найти любые | + | Дан массив целых чисел <tex>{A}</tex>. Требуется найти любые <tex> 4 </tex> числа, сумма которых равна <tex> 0 </tex> (одинаковые элементы могут быть использованы несколько раз). |
Например : <tex> {A} = ({2,3,1,0,-4,-1}) </tex>. Решением данной задачи является, например, четверка чисел <tex> 3 + 1 + 0 - 4 = 0</tex> или <tex> 0 + 0 + 0 + 0 = 0</tex>. | Например : <tex> {A} = ({2,3,1,0,-4,-1}) </tex>. Решением данной задачи является, например, четверка чисел <tex> 3 + 1 + 0 - 4 = 0</tex> или <tex> 0 + 0 + 0 + 0 = 0</tex>. | ||
| Строка 16: | Строка 16: | ||
=== Реализация === | === Реализация === | ||
// sum - массив сумм a + b, cnt - счетчик массива sum | // sum - массив сумм a + b, cnt - счетчик массива sum | ||
| − | '''findsum'''() | + | '''findsum'''() |
'''for''' a = 0..N - 1 | '''for''' a = 0..N - 1 | ||
'''for''' b = 0..N - 1 | '''for''' b = 0..N - 1 | ||
| Строка 23: | Строка 23: | ||
sum[cnt].b = b | sum[cnt].b = b | ||
cnt++ | cnt++ | ||
| − | '''sort'''(sum, key="res") // сортируем sum по полю res | + | '''sort'''(sum, key = "res") // сортируем sum по полю res |
'''for''' c = 0..N - 1 | '''for''' c = 0..N - 1 | ||
'''for''' d = 0..N - 1 | '''for''' d = 0..N - 1 | ||
| Строка 31: | Строка 31: | ||
'''return''' "No solution" | '''return''' "No solution" | ||
Итоговое время работы <tex> {O(N^2\log{N}}) </tex>. | Итоговое время работы <tex> {O(N^2\log{N}}) </tex>. | ||
| + | |||
| + | Если вместо отсортированного массива использовать [[Хеш-таблица | хэш-таблицу]], то задачу можно будет решить за время <tex> O(N^2) </tex>. | ||
== Задача о рюкзаке == | == Задача о рюкзаке == | ||
| − | Классической задачей является задача о наиболее эффективной упаковке рюкзака. Каждый предмет характеризуется весом (<tex> {w_{i} \ | + | Классической задачей является задача о наиболее эффективной упаковке рюкзака. Каждый предмет характеризуется весом (<tex> {w_{i} \leqslant 10^{9}} </tex> ) и ценностью (<tex>{cost_{i} \leqslant 10^{9}} </tex>). В рюкзак, ограниченный по весу, необходимо набрать вещей с максимальной суммарной стоимостью. Для ее решения изначальное множество вещей N разбивается на два равных(или примерно равных) подмножества, для которых за приемлемое время можно перебрать все варианты и подсчитать суммарный вес и стоимость, а затем для каждого из них найти группу вещей из первого подмножества с максимальной стоимостью, укладывающуюся в ограничение по весу рюкзака. Сложность алгоритма <tex>O({2^{N/2}}\cdot{N})</tex>. Память <tex> O({2^{N/2}})</tex>. |
=== Реализация === | === Реализация === | ||
| Строка 43: | Строка 45: | ||
Реализуем данный алгоритм: | Реализуем данный алгоритм: | ||
// N - количество всех вещей, w[] - массив весов всех вещей, cost[] - массив стоимостей всех вещей, R - ограничение по весу рюкзака. | // N - количество всех вещей, w[] - массив весов всех вещей, cost[] - массив стоимостей всех вещей, R - ограничение по весу рюкзака. | ||
| − | '''knapsack'''() | + | '''knapsack'''() |
sn = N / 2 | sn = N / 2 | ||
| − | fn = N - sn | + | fn = N - sn |
'''for''' mask = 0..2 ** sn - 1 | '''for''' mask = 0..2 ** sn - 1 | ||
'''for''' j = 0 .. sn | '''for''' j = 0 .. sn | ||
| − | '''if''' j-ый бит mask = 1 | + | '''if''' j-ый бит mask == 1 |
first[i].w += w[j]; | first[i].w += w[j]; | ||
| − | first[i].c += cost[j] | + | first[i].c += cost[j] |
сортируем first по весу | сортируем first по весу | ||
'''for''' i = 0 .. 2 ** sn - 1 | '''for''' i = 0 .. 2 ** sn - 1 | ||
| − | '''if''' существует такое подмножество с индексом j, что first[j].w < | + | '''if''' существует такое подмножество с индексом j, что first[j].w <tex> \leqslant </tex> first[i].w && first[j].c <tex> \geqslant </tex> first[i].c |
удалим множество с индексом i из массива first | удалим множество с индексом i из массива first | ||
'''for''' mask = 0..2 ** fn - 1 | '''for''' mask = 0..2 ** fn - 1 | ||
'''for''' j = 0..fn | '''for''' j = 0..fn | ||
| − | '''if''' j-ый бит mask = 1 | + | '''if''' j-ый бит mask == 1 |
| − | curw += w[j + sn] | + | curw += w[j + sn] |
| − | curcost += cost[j + sn] | + | curcost += cost[j + sn] |
index = позиция, найденная бинарным поиском в массиве first, подмножества с максимальным весом, не превыщающим R - curv | index = позиция, найденная бинарным поиском в массиве first, подмножества с максимальным весом, не превыщающим R - curv | ||
| − | '''if''' first[index].w < | + | '''if''' first[index].w <tex> \leqslant </tex> R - curw && first[index].c + curcost > ans |
ans = first[index].c + curcost | ans = first[index].c + curcost | ||
'''return''' ans | '''return''' ans | ||
| − | Итоговое время работы <tex> {O({2^{N/2}}\ | + | Итоговое время работы <tex> {O({2^{N/2}}\cdot({N}+\log{2^{N/2}}))} = O({2^{N/2}}\times{N}) </tex>. |
== Задача о нахождении кратчайшего расстояния между двумя вершинами в графе == | == Задача о нахождении кратчайшего расстояния между двумя вершинами в графе == | ||
[[Файл:bfs.png|600px|thumb|right|Нахождение кратчайшего расстояния между двумя вершинами]] | [[Файл:bfs.png|600px|thumb|right|Нахождение кратчайшего расстояния между двумя вершинами]] | ||
Еще одна задача, решаемая '''Meet-in-the-middle''' — это нахождение кратчайшего расстояния между двумя вершинами, зная начальное состояние, конечное состояние и то, что длина оптимального пути не превышает <tex> N </tex>. | Еще одна задача, решаемая '''Meet-in-the-middle''' — это нахождение кратчайшего расстояния между двумя вершинами, зная начальное состояние, конечное состояние и то, что длина оптимального пути не превышает <tex> N </tex>. | ||
| − | Стандартным подходом для решения данной задачи, является применение алгоритма [[Обход в ширину|обхода в ширину]]. Пусть из каждого состояния у нас есть | + | Стандартным подходом для решения данной задачи, является применение алгоритма [[Обход в ширину|обхода в ширину]]. Пусть из каждого состояния у нас есть <tex> K </tex> переходов, тогда бы мы сгенерировали <tex> {K^{N}} </tex> состояний. Асимптотика данного решения составила бы <tex> {O({K^{N}})} </tex>. '''Meet-in-the-middle''' помогает снизить асимптотику до <tex> {O({K^{N/2}})} </tex>. <br> |
=== Алгоритм решения === | === Алгоритм решения === | ||
Версия 20:37, 15 ноября 2013
| Определение: |
| Meet-in-the-middle (Встреча в середине) — это метод решения уравнения вида , где и , который работает за время , где — время поиска элемента в множестве , удовлетворяющее решению при заданном , или проверка, что такого не существует. |
Meet-in-the-middle разбивает задачу пополам и решает всю задачу через частичный расчет половинок. Он работает следующим образом: переберем все возможные значения и запишем пару значений в множество. Затем будем перебирать всевозможные значения , для каждого из них будем вычислять , которое мы будем искать в нашем множестве. Если в качестве множества использовать отсортированный массив, а в качестве функции поиска — бинарный поиск, то время работы нашего алгоритма составляет на сортировку, и на двоичный поиск, что дает в сумме .
Содержание
Задача о нахождение четырех чисел с суммой равной нулю
Дан массив целых чисел . Требуется найти любые числа, сумма которых равна (одинаковые элементы могут быть использованы несколько раз).
Например : . Решением данной задачи является, например, четверка чисел или .
Наивный алгоритм заключается в переборе всевозможных комбинаций чисел. Это решение работает за . Теперь, с помощью Meet-in-the-middle мы можем сократить время работы до .
Для этого заметим, что сумму можно записать как . Мы будем хранить все пар сумм в массиве , который мы отсортируем. Далее перебираем все пар сумм и проверяем бинарным поиском, есть ли сумма в массиве .
Реализация
// sum - массив сумм a + b, cnt - счетчик массива sum
findsum()
for a = 0..N - 1
for b = 0..N - 1
sum[cnt].res = A[a] + B[b]
sum[cnt].a = a
sum[cnt].b = b
cnt++
sort(sum, key = "res") // сортируем sum по полю res
for c = 0..N - 1
for d = 0..N - 1
if сумма -(A[c] + A[d]) есть в массив sum
index = индекс суммы -(A[c] + A[d])
return (sum[index].a, sum[index].b, A[c], A[d])
return "No solution"
Итоговое время работы .
Если вместо отсортированного массива использовать хэш-таблицу, то задачу можно будет решить за время .
Задача о рюкзаке
Классической задачей является задача о наиболее эффективной упаковке рюкзака. Каждый предмет характеризуется весом ( ) и ценностью (). В рюкзак, ограниченный по весу, необходимо набрать вещей с максимальной суммарной стоимостью. Для ее решения изначальное множество вещей N разбивается на два равных(или примерно равных) подмножества, для которых за приемлемое время можно перебрать все варианты и подсчитать суммарный вес и стоимость, а затем для каждого из них найти группу вещей из первого подмножества с максимальной стоимостью, укладывающуюся в ограничение по весу рюкзака. Сложность алгоритма . Память .
Реализация
Разделим наше множество на две части. Подсчитаем все подмножества из первой части и будем хранить их в массиве . Отсортируем массив по весу. Далее пройдемся по этому массиву и оставим только те подмножества, для которых не существует другого подмножества с меньшим весом и большей стоимостью. Очевидно, что подмножества, для которых существует другое, более легкое и одновременно более ценное подмножество, можно удалять. Таким образом в массиве мы имеем подмножества, отсортированные не только по весу, но и по стоимости. Тогда начнем перебирать все возможные комбинации вещей из второй половины и находить бинарным поиском удовлетворяющие нам подмножества из первой половине, хранящиеся в массиве .
Реализуем данный алгоритм:
// N - количество всех вещей, w[] - массив весов всех вещей, cost[] - массив стоимостей всех вещей, R - ограничение по весу рюкзака.
knapsack()
sn = N / 2
fn = N - sn
for mask = 0..2 ** sn - 1
for j = 0 .. sn
if j-ый бит mask == 1
first[i].w += w[j];
first[i].c += cost[j]
сортируем first по весу
for i = 0 .. 2 ** sn - 1
if существует такое подмножество с индексом j, что first[j].w first[i].w && first[j].c first[i].c
удалим множество с индексом i из массива first
for mask = 0..2 ** fn - 1
for j = 0..fn
if j-ый бит mask == 1
curw += w[j + sn]
curcost += cost[j + sn]
index = позиция, найденная бинарным поиском в массиве first, подмножества с максимальным весом, не превыщающим R - curv
if first[index].w R - curw && first[index].c + curcost > ans
ans = first[index].c + curcost
return ans
Итоговое время работы .
Задача о нахождении кратчайшего расстояния между двумя вершинами в графе
Еще одна задача, решаемая Meet-in-the-middle — это нахождение кратчайшего расстояния между двумя вершинами, зная начальное состояние, конечное состояние и то, что длина оптимального пути не превышает .
Стандартным подходом для решения данной задачи, является применение алгоритма обхода в ширину. Пусть из каждого состояния у нас есть переходов, тогда бы мы сгенерировали состояний. Асимптотика данного решения составила бы . Meet-in-the-middle помогает снизить асимптотику до .
Алгоритм решения
1. Сгенерируем bfs-ом все состояния, доступные из начала и конца за или меньше ходов.
2. Найдем состояния, которые достижимы из начала и из конца.
3. Найдем среди них наилучшее по сумме длин путей.
Таким образом, bfs-ом из двух концов, мы сгенерируем максимум состояний.
