ДП по поддеревьям
Оглавление
Запись занятия
Максимальное паросочетание в дереве neerc
Необходимо найти максимальное паросочетание в дереве за линейное время.
Паросочетание (англ. matсhing) M в двудольном графе - произвольное множество рёбер графа такое, что никакие два ребра не имеют общей вершины.
Решение
dp[v][0/1] = размеру максимального паросочетания в поддереве вершины v, если 0 - не взяли v, 1 - взяли v.
Окей, тогда придумаем переходы:
dp[v][0] = ∑max(dp[u][0], dp[u][1]) по всем u - детям v
Т.е. из поддерева каждого сына мы выбираем оптимальное паросочетание. А вот в случае, когда мы хотим покрыть v паросочетанием, всё несколько сложнее. Мы должны выбрать сына u, который не был бы покрыт паросочетанием, взять ребро v-u и ещё из остальных поддеревьев взять по оптимальному ответу, т.е. формула будет иметь вид:
dp[v][1] = maxu - сын dp[u][0] + 1 + ∑w - cын, u != wmax(dp[w][0], dp[w][1])
Замечаем, что пока это квадрат, но прибавим и вычтем к сумме max(dp[u][0], dp[u][1]). Тогда под суммой окажется значение, равное dp[v][0]. Т.е. получили итоговую формулу вида:
dp[v][1] = maxu - сын v dp[v][0] - max(dp[u][0], dp[u][1]) + dp[u][0] + 1
Код
1
2
3
4
5
6
7
8
9
10
11
12
13
void dfs(int v, int p = -1) {
for (int u : gr[v]) {
if (u != p) {
dfs(u, v);
dp[v][0] += max(dp)
}
}
for (int u : gr[v]) {
if (u != p) {
dp[v][1] = max(dp[v][1], dp[v][0] - max(dp[u][0], dp[u][1]) + dp[u][0] + 1);
}
}
}
Диаметр через каждую вершину
В дереве для каждой вершины необходимо найти самый длинный путь, проходящий через неё за линейное время.
Решение
Пусть dp[v] - обозначает самый длинный путь в поддереве. Тогда самый длинный путь, проходящий через v строго в поддереве - это dp[u1] + dp[u2] + 2, где u1 и u2 - это максимум и второй максимум по сыновьям v. Главная проблема в том, что пути, проходящие через веришину v могут уходить в наддерево v. Но сейчас разберем и такой случай!
Чтобы решить проблему заметим, что у корня дерева наддерева нет. А ещё заметим, что мы можем выбрать поддерево u корня v, и при переходе в поддерево передать в него значение самого длинного пути в наддереве - dp[w] + 2, где w - это какая-то вершина, отличная от u и имеющая при этом максимальное значение динамики. Ок. То есть, начав с корня, мы можем начитывать пути в наддеревья. Остался один маленький нюанс. Нужно уметь считать dp[w] за O(1), то есть для всех детей искать максимум по сыновьям кроме u.
Для этого трюк: запомним максимум и второй максимум. Если u - это позиция максимума, то ответ - второй максимум, для остальных - позиция максимума. Всё, теперь, собирая решение воедино, мы сможем за линию считать диаметр дерева через каждую вершину.
Количество связных подграфов на K вершинах cf post
Дано дерево. Необходимо вычислить количество связных подграфов размера K в нём за время:
[a] O(nk2) </br> [b] O(nk)
dp[v][k] - это количество связных подграфов размера k в поддереве v, если v - корень. Тогда научимся считать динамику трюком: сразу считать всю дп сложно и непонятно. Вместо этого будем добавлять по одному поддереву. То есть у нас будут промежуточные состояния: dp[v][k][t], которое означает количество связных подграфов размера k в поддереве v, если v - корень и мы рассмотрели первые t детей v. Переходы будет проще описать кодом:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void solve(int v, int p = -1)
{
dp[v][0] = 1;
dp[v][1] = 1;
for (int u : gr[v]) {
if (u == p) continue;
solve(u, v);
fill(tmp , tmp + k + 1 , 0);
for (int i = 1; i <= k; i++)
for (int j = 0; i + j <= K; j++)
tmp[i + j] += dp[v][i] * dp[u][j];
for(int i = 0; i <= min(K , Sub[v]); i++)
dp[v][i] = tmp[i];
}
}
Это решение за чистый квадрат по k. Оказывается, что если идти до min(k, размера поддерева u), то асимптотика превратится в O(nk). Доказательства я не знаю. Т.е. Решение превратится в такое:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void solve(int v, int p = -1)
{
Sub[v] = 1;
dp[v][0] = 1;
dp[v][1] = 1;
for (int u : gr[v]) {
if (u == p) continue;
solve(u, v);
fill(tmp , tmp + k + 1 , 0);
for(int i = 1; i <= min(Sub[v] , k); i++)
for(int j = 0; j <= Sub[u] && i + j <= K; j++)
tmp[i + j] += dp[v][i] * dp[u][j];
Sub[v] += Sub[u];
for(int i = 0; i <= min(K , Sub[v]); i++)
dp[v][i] = tmp[i];
}
}