Skip to Content

DP en árboles - Introducción

HechoFuenteNombreDificultadTagsSolución
CSESTree MatchingFácilDPen el módulo

Tutorial

Recursos
FuenteRecursoNotas
CFDP on Trees
PhilippinesDP on Trees and DAGs

formato de código malo

Solución - Tree Matching

Solución 1

En este problema hay que hallar el matching máximo de un árbol, o sea, el conjunto más grande de aristas tal que no haya dos que compartan un extremo. Usemos DP en árboles para hacerlo.

Enraizamos el árbol en el nodo 11, lo que nos permite definir el subárbol de cada nodo.

Sea dp2[v]dp_2[v] el matching máximo del subárbol de vv tal que no tomamos ninguna arista que lleve a algún hijo de vv. De forma similar, sea dp1[v]dp_1[v] el matching máximo del subárbol de vv tal que tomamos una arista que entra a un hijo de vv. Nótese que no podemos tomar más de una arista que lleve a un hijo, porque entonces dos aristas compartirían un extremo.

Sin tomar aristas

Como no tomaremos aristas hacia un hijo de vv, los vértices hijos de vv pueden tomar o no una arista hacia algún hijo. Además, observemos que el hecho de que un hijo de vv tome una arista hacia un hijo no impide que otros hijos de vv hagan lo mismo. En otras palabras, todos los hijos son independientes. Así, las transiciones son:

dp2[v]=uchild(v)max(dp1[u],dp2[u]) dp_2[v] = \sum_{u \in child(v)} \max(dp_1[u], dp_2[u])

Tomando una arista

El caso en el que tomamos una arista a un hijo de vv es un poco más delicado. Supongamos que la arista que tomamos es vuv \rightarrow u, donde uchild(v)u \in child(v). Entonces, para calcular dp1[v]dp_1[v] con uu fijo:

dp1[v]=dp2[u]+1+dp2[v]max(dp2[u],dp1[u]) dp_1[v] = dp_2[u] + 1 + dp_2[v] - \max(dp_2[u], dp_1[u])

En otras palabras, tomamos la arista vuv \rightarrow u, pero no podemos tomar ningún hijo de uu en el matching, así que sumamos dp2[u]+1dp_2[u] + 1. Luego, para tratar a los demás hijos, sumamos:

wchild(v),wumax(dp1[w],dp2[w]). \sum_{w \in child(v), w \neq u} \max(dp_1[w], dp_2[w]).

Por suerte, como ya calculamos dp2[v]dp_2[v], esta expresión se simplifica a:

dp2[v]max(dp2[u],dp1[u]) dp_2[v] - \max(dp_2[u], dp_1[u])

En total, para calcular las transiciones de dp1[v]dp_1[v] sobre todos los hijos posibles uu:

dp1[v]=maxuchild(v)(dp2[u]+1+dp2[v]max(dp2[u],dp1[u])) dp_1[v] = \max_{u \in child(v)} (dp_2[u] + 1 + dp_2[v] - \max(dp_2[u], dp_1[u]))
#include <bits/stdc++.h> using namespace std; using ll = long long; using vi = vector<int>; #define pb push_back #define rsz resize #define all(x) begin(x), end(x) #define sz(x) (int)(x).size() using pi = pair<int, int>; #define f first #define s second #define mp make_pair void setIO(string name = "") { // name no vacío para E/S por archivos de USACO ios_base::sync_with_stdio(0); cin.tie(0); // ver Fast Input & Output if (sz(name)) { freopen((name + ".in").c_str(), "r", stdin); // ver Input & Output freopen((name + ".out").c_str(), "w", stdout); } } vi adj[200005]; int dp[200005][2]; void dfs(int v, int p) { for (int to : adj[v]) { if (to != p) { dfs(to, v); dp[v][0] += max(dp[to][0], dp[to][1]); } } for (int to : adj[v]) { if (to != p) { dp[v][1] = max(dp[v][1], dp[to][0] + 1 + dp[v][0] - max(dp[to][0], dp[to][1])); } } } int main() { setIO(); int n; cin >> n; for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; u--, v--; adj[u].pb(v), adj[v].pb(u); } dfs(0, -1); cout << max(dp[0][0], dp[0][1]) << '\n'; }
import java.io.*; import java.util.*; public class TreeMatching { static ArrayList<Integer> adj[]; static int dp[][]; static void dfs(int v, int p) { for (int to : adj[v]) { if (to != p) { dfs(to, v); dp[v][0] += Math.max(dp[to][0], dp[to][1]); } } for (int to : adj[v]) { if (to != p) { dp[v][1] = Math.max(dp[v][1], dp[to][0] + 1 + dp[v][0] - Math.max(dp[to][0], dp[to][1])); } } } public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int N = Integer.parseInt(br.readLine()); adj = new ArrayList[N]; dp = new int[N][2]; for (int i = 0; i < N; ++i) { adj[i] = new ArrayList<Integer>(); } for (int i = 0; i < N - 1; ++i) { StringTokenizer st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()) - 1; int b = Integer.parseInt(st.nextToken()) - 1; adj[a].add(b); adj[b].add(a); } dfs(0, -1); System.out.println(Math.max(dp[0][0], dp[0][1])); } }

Solución 2 - Voraz

Basta con ir emparejando una hoja con el único vértice adyacente mientras sea posible.

int n; vi adj[MX]; bool done[MX]; int ans = 0; void dfs(int pre, int cur) { for (int i : adj[cur]) { if (i != pre) { dfs(cur, i); if (!done[i] && !done[cur]) done[cur] = done[i] = 1, ans++; } } } int main() { ios_base::sync_with_stdio(0); cin.tie(0); cin >> n; F0R(i, n - 1) { int a, b; cin >> a >> b; adj[a].pb(b), adj[b].pb(a); } dfs(0, 1); cout << ans; }
import java.io.*; import java.util.*; public class TreeMatching { static ArrayList<Integer> adj[]; static int N; static boolean done[]; static int ans = 0; static void dfs(int v, int p) { for (int to : adj[v]) { if (to != p) { dfs(to, v); if (!done[to] && !done[v]) { done[v] = done[to] = true; ++ans; } } } } public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); N = Integer.parseInt(br.readLine()); adj = new ArrayList[N]; done = new boolean[N]; for (int i = 0; i < N; ++i) { adj[i] = new ArrayList<Integer>(); } for (int i = 0; i < N - 1; ++i) { StringTokenizer st = new StringTokenizer(br.readLine()); int a = Integer.parseInt(st.nextToken()) - 1; int b = Integer.parseInt(st.nextToken()) - 1; adj[a].add(b); adj[b].add(a); } dfs(0, -1); System.out.println(ans); } }

Problemas

Más fáciles

HechoFuenteNombreDificultadTagsSolución
ACIndependent SetFácilTree, DPSolución
GoldBarn PaintingFácilTree, DPSolución
CFInfected TreeFácilDP, Tree
CFDistance in TreeNormalTree, DP
Baltic OI2020 - Village (Minimum)NormalTree, GreedySolución
CFNastia Plays with a TreeNormalTree, Greedy
POIParadeNormalTreeSolución
CFBerland FederalizationNormalTree, DPSolución
CFParsa's Humongous TreeNormalTree, DPSolución
ACSelect EdgesNormalTree, DPSolución
GoldDelegationNormalTree, GreedySolución
PlatinumDelegationNormalDP, Tree, Binary SearchSolución
GoldBessie's FunctionNormalDP, Tree
POI2004 - SpiesDifícilFunctional GraphSolución
KattisGCD HarmonyDifícilDP, Tree, Number TheorySolución
POI2008 - MafiaDifícilFunctional GraphSolución

Más difíciles

Estos problemas no son de nivel Oro. Conviene volver a ellos cuando se esté en Platino.

HechoFuenteNombreDificultadTagsSolución
COI2016 - TorrentMuy difícilDP, TreeSolución
CFBoboniu and JianghuMuy difícilDP, Tree
IOI2007 - TrainingInsanoDP, Tree
CSESCreating OfficesInsanoTree, Greedy
Baltic OI2016 - SwapInsanoTree, DPSolución