Динамика по поддеревьям

Материал из Викиконспекты
Версия от 21:29, 13 января 2013; Mihver1 (обсуждение | вклад) (Рекуррентная формула)
Перейти к: навигация, поиск

Динамика по деревьям

Главной особенностью динамического программирования по дереву является необходимость учитывать ответы в поддеревьях, т.к. они могут влиять на другие поддеревья. Рассмотрим для лучшего понимания динамики по поддеревьям задачу о максимальном взвешенном паросочетании в дереве.

Задача о максимальном взвешенном паросочетании на дереве

Формулировка

Пусть дано подвешенное за корень дерево, имеющее веса на каждом из ее ребер. Необходимо выбрать такое множество ребер, что бы сумма значений была максимальной и при этом выбранные ребра не являлись бы соседними. Т.е. необходимо решить задачу о максимальном взвешенном паросочетании.

Решение

Максимальное взвешенное паросочетание

Главное отличие этой задачи от других динамически решаемых — ответ в одном поддереве влияет на решение в остальных.

Рассмотрим наше первое состояние, когда еще не выбрана ни одна вершина. В этом случае мы можем сделать две вещи:

  • Занять корень каким-то ребром
  • Не занимать корень ребрами

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

Рекуррентная формула

Обозначим в качестве [math]dp(u, root)[/math] функцию, возвращающую ответ для поддерева с корнем [math]u[/math]. Если [math]root=1[/math], то в этом поддереве мы разрешаем занимать корень. Иначе нет.

[math]dp(u, 0) = \sum_{\text{child}\ v\ of\ u}dp(w, 1)[/math]
[math]dp(u, 1) = \max\left\{dp(u, 0),\ \max_{\text{child}\ x\ of\ u}\{dp(x, 0)\ +\ \sum_{\text{child}\ v\ of\ u; \ v \ne x }dp(v, 1)\ +\ a[u] \}\right\}[/math]

Заметим, что вторую формулу можно упростить:
[math]\sum_{\text{child}\ v\ of\ u; \ v \ne x }dp(v, 1) = dp(u, 0) - dp(x, 1)[/math]

Теперь наши формулы имеют вид:
[math]dp(u, 0) = \sum_{\text{child}\ v\ of\ u}dp(w, 1)[/math]
[math]dp(u, 1) = \max\left\{dp(u, 0),\ \max_{\text{child}\ x\ of\ u}\{dp(x, 0)\ +\ dp(u, 0) - dp(x, 1)\ +\ w[u,x] \}\right\}[/math]

Заметим, что с помощью этого преобразования мы сократили общее время вычисления с [math]O(n^2)[/math] до [math]O(n)[/math].

Псевдокод

   function calculate(v, root):
       if dp[v][root] != -1:
           return dp[v][root]
           #вернули уже посчитанное значение dp[v][root]
       sum1 = 0
       #случай 1: не берем ребра из корня
       if root==0:
           for u in child(v):
               sum1 += calculate(u, 1)
           #выполняем мемоизацию
           dp[v][root] = sum1
           return sum1
       max1 = dp[v][0]
       #случай 2: берем какое-то ребро
       for x in child(v):
           max1 = max(max1, calculate(x, 0) + calculate(v, 0) - calculate(x, 1) + w[v,x])
       # выполняем мемоизацию
       dp[v][root] = max1
       return dp[v][root]