ДП по поддеревьям
Оглавление
Запись занятия
Максимальное паросочетание в дереве 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 - это позиция максимума, то ответ - второй максимум, для остальных - позиция максимума. Всё, теперь, собирая решение воедино, мы сможем за линию считать диаметр дерева через каждую вершину.
Код за $O(n^2)$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5;
vector<int> gr[N];
int dp[N];
int h[N];
int ans[N];
void dfs1(int v, int p = -1) {
h[v] = 0;
int max1 = 0;
int max2 = -1;
for (int u : gr[v]) {
if (u != p) {
dfs1(u, v);
h[v] = max(h[v], h[u] + 1);
if (h[u] >= max1) {
max2 = max1;
max1 = h[u];
}
else if (h[u] > max2) {
max2 = h[u];
}
}
}
if (max2 == -1) {
dp[v] = max1 + 1;
}
else {
dp[v] = max1 + max2 + 2;
}
}
void dfs2(int v, int p = -1, int max_supertree = 0) {
ans[v] = max(ans[v], h[v] + max_supertree);
for (int u : gr[v]) {
if (u != p) {
int new_max_supertree = max_supertree + 1;
for (int w : gr[v]) {
if (w != p && w != u) {
new_max_supertree = max(new_max_supertree, h[w] + 2);
}
}
dfs2(u, v, new_max_supertree);
}
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i < n; ++i) {
int a, b;
cin >> a >> b;
--a, --b;
gr[a].push_back(b);
gr[b].push_back(a);
}
dfs1(0);
dfs2(0);
for (int i = 0; i < n; ++i) {
cout << ans[i] << ' ';
}
}
Код за $O(n)$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e5;
vector<int> gr[N];
int dp[N];
int h[N];
int ans[N];
void dfs1(int v, int p = -1) {
h[v] = 0;
int max1 = 0;
int max2 = -1;
for (int u : gr[v]) {
if (u != p) {
dfs1(u, v);
h[v] = max(h[v], h[u] + 1);
if (h[u] >= max1) {
max2 = max1;
max1 = h[u];
}
else if (h[u] > max2) {
max2 = h[u];
}
}
}
if (max2 == -1) {
dp[v] = max1 + 1;
}
else {
dp[v] = max1 + max2 + 2;
}
}
void dfs2(int v, int p = -1, int max_supertree = 0) {
ans[v] = max(ans[v], h[v] + max_supertree);
vector<int> pref, suf;
for (int u : gr[v]) {
if (u != p) {
pref.push_back(h[u]);
suf.push_back(h[u]);
}
}
for (int i = 1; i < pref.size(); ++i) {
pref[i] = max(pref[i - 1], pref[i]);
}
for (int i = int(pref.size()) - 2; i >= 0; --i) {
suf[i] = max(suf[i + 1], suf[i]);
}
int i = 0;
for (int u : gr[v]) {
if (u != p) {
int new_max_supertree = max({ max_supertree + 1,
i == 0 ? 0 : pref[i - 1] + 2,
i + 1 == suf.size() ? 0 : suf[i + 1] + 2 });
dfs2(u, v, new_max_supertree);
i++;
}
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i < n; ++i) {
int a, b;
cin >> a >> b;
--a, --b;
gr[a].push_back(b);
gr[b].push_back(a);
}
dfs1(0);
dfs2(0);
for (int i = 0; i < n; ++i) {
cout << ans[i] << ' ';
}
}
Количество связных подграфов на K вершинах cf post
Дано дерево. Необходимо вычислить количество связных подграфов размера K в нём за время: [a] $O(nk^2)$ [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];
}
}
Сумма длин всех путей в дереве neerc
Найти сумму длин всех путей в дереве.
Коровы в стойла на дереве informatics
Дано дерево на $n$ вершинах и число $k$. В каждой вершине дерева находится по одной корове. Необходимо выбрать в каких вершинах построить $k$ стойл так, чтобы максимальный путь от коровы до ближайшего стойла был как можно меньше.