DP en árboles - Introducción
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| CSES | Tree Matching | Fácil | DP | en el módulo |
Tutorial
| Fuente | Recurso | Notas |
|---|---|---|
| CF | DP on Trees | |
| Philippines | DP 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 , lo que nos permite definir el subárbol de cada nodo.
Sea el matching máximo del subárbol de tal que no tomamos ninguna arista que lleve a algún hijo de . De forma similar, sea el matching máximo del subárbol de tal que tomamos una arista que entra a un hijo de . 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 , los vértices hijos de pueden tomar o no una arista hacia algún hijo. Además, observemos que el hecho de que un hijo de tome una arista hacia un hijo no impide que otros hijos de hagan lo mismo. En otras palabras, todos los hijos son independientes. Así, las transiciones son:
Tomando una arista
El caso en el que tomamos una arista a un hijo de es un poco más delicado. Supongamos que la arista que tomamos es , donde . Entonces, para calcular con fijo:
En otras palabras, tomamos la arista , pero no podemos tomar ningún hijo de en el matching, así que sumamos . Luego, para tratar a los demás hijos, sumamos:
Por suerte, como ya calculamos , esta expresión se simplifica a:
En total, para calcular las transiciones de sobre todos los hijos posibles :
#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
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| AC | ★ Independent Set | Fácil | Tree, DP | Solución | |
| Gold | Barn Painting | Fácil | Tree, DP | Solución | |
| CF | Infected Tree | Fácil | DP, Tree | — | |
| CF | Distance in Tree | Normal | Tree, DP | — | |
| Baltic OI | 2020 - Village (Minimum) | Normal | Tree, Greedy | Solución | |
| CF | Nastia Plays with a Tree | Normal | Tree, Greedy | — | |
| POI | Parade | Normal | Tree | Solución | |
| CF | Berland Federalization | Normal | Tree, DP | Solución | |
| CF | Parsa's Humongous Tree | Normal | Tree, DP | Solución | |
| AC | Select Edges | Normal | Tree, DP | Solución | |
| Gold | Delegation | Normal | Tree, Greedy | Solución | |
| Platinum | Delegation | Normal | DP, Tree, Binary Search | Solución | |
| Gold | Bessie's Function | Normal | DP, Tree | — | |
| POI | 2004 - Spies | Difícil | Functional Graph | Solución | |
| Kattis | GCD Harmony | Difícil | DP, Tree, Number Theory | Solución | |
| POI | 2008 - Mafia | Difícil | Functional Graph | Solución |
Más difíciles
Estos problemas no son de nivel Oro. Conviene volver a ellos cuando se esté en Platino.
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| COI | 2016 - Torrent | Muy difícil | DP, Tree | Solución | |
| CF | Boboniu and Jianghu | Muy difícil | DP, Tree | — | |
| IOI | ★ 2007 - Training | Insano | DP, Tree | — | |
| CSES | Creating Offices | Insano | Tree, Greedy | — | |
| Baltic OI | 2016 - Swap | Insano | Tree, DP | Solución |