09
Programación dinámica

Más programación dinámica

Programación dinámica

Semana 9 — Más programación dinámica

Objetivos

  • Ver que el grafo de subproblemas de una programación dinámica es un DAG.
  • Resolver problemas sobre intervalos, sobre DAG y sobre árboles.
  • Elegir el orden de cálculo cuando no es una simple tabla de izquierda a derecha.

Sesión A — Programación dinámica sobre intervalos y sobre DAG

Lectura: Apuntes, semana 9.

Apoyo: Erickson, secs. 3.6 (Longest Increasing Subsequence), 3.9 (Optimal Binary Search Trees) y 6.4 (Memoization and Dynamic Programming). CLRS, capítulo Dynamic Programming, 4.ª edición.

Temas:

  • Los subproblemas y sus dependencias forman un DAG. Cuando cada decisión depende de un solo subproblema, la solución es un camino en ese DAG; en intervalos y árboles se combinan varios.
  • Camino más largo en un DAG, usando el orden topológico de la semana 6. Sin DAG, ese problema se vuelve difícil: semana 14.
  • Subsecuencia creciente más larga en O(n²), y mejora a O(n log n) con búsqueda binaria.
  • Intervalos: multiplicar una cadena de matrices, donde el subproblema es un rango i..j. Costo O(n³).
  • Calcular por largo de intervalo: por qué el orden i, j ingenuo lee casillas sin llenar.
  • Árboles binarios de búsqueda óptimos, con las mismas ideas.
  • Error común: usar un orden de cálculo que lee una casilla antes de llenarla.

Sesión B — Programación dinámica sobre árboles

Lectura: Apuntes, semana 9.

Apoyo: Erickson, sec. 3.10 (Dynamic Programming on Trees).

Temas:

  • Un árbol tiene un orden natural: primero los hijos.
  • Conjunto independiente de peso máximo en un árbol: dos estados por nodo, con él y sin él.
  • Reconstruir la solución.
  • Cobertura de vértices mínima en un árbol: el mismo patrón. Ambos son difíciles en un grafo general: semana 13.
  • Evitar la recursión profunda: procesar los nodos en orden inverso de un BFS.
  • Error común: olvidar que un nodo sin elegir deja libres a sus hijos: pueden elegirse o no.

Laboratorio — Optimizar sobre una jerarquía

  1. Una empresa tiene forma de árbol, y cada persona aporta un valor si asiste a una reunión. Nadie quiere asistir junto con su jefe directo. Calcular el mayor valor posible.
  2. Probar primero con fuerza bruta en árboles pequeños, y comparar.
  3. Pasar los casos grandes, incluido un árbol que es una sola cadena, donde la recursión produce un stack overflow.
  4. Extensión: imprimir también quiénes asisten. Un verificador comprueba la lista.

Preguntas de repaso

  1. Dibujen el DAG de subproblemas de Fibonacci y el de la distancia de edición.
  2. Calculen la mejor forma de multiplicar matrices de 10×30, 30×5 y 5×60.
  3. ¿Por qué el orden i, j ingenuo falla en la tabla de intervalos, y por qué funciona llenarla por largo creciente? ¿Es el único orden posible?
  4. Escriban la recurrencia del conjunto independiente de peso máximo en un árbol.
  5. ¿Por qué ese algoritmo no sirve directamente en un grafo con ciclos?

Código de la semana

Compilar y probar dentro del contenedor del curso con make y make test.

Ejemplos (12)Pruebas (1)Laboratorio (7)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

bst_demo.cppDescargar bst_demo.cpp
// Árbol binario de búsqueda óptimo: los greedy que fallan, la tabla y el árbol que sale.
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

#include "interval_dp.h"

namespace {

void show(const std::vector<std::string>& names, const std::vector<long long>& f) {
    const int n = static_cast<int>(f.size());
    std::cout << "Claves y frecuencias:";
    for (int i = 0; i < n; i++) std::cout << ' ' << names[i] << '=' << f[i];
    const BstTables t = optimal_bst(f);
    std::cout << "\n  C[i][j], rango [i, j)\n  i\\j";
    for (int j = 0; j <= n; j++) std::cout << std::setw(5) << j;
    std::cout << '\n';
    for (int i = 0; i <= n; i++) {
        std::cout << std::setw(5) << i;
        for (int j = 0; j <= n; j++) {
            if (j < i) {
                std::cout << std::setw(5) << ".";
            } else {
                std::cout << std::setw(5) << t.cost[i][j];
            }
        }
        std::cout << '\n';
    }
    std::cout << "  raíz[i][j]\n  i\\j";
    for (int j = 1; j <= n; j++) std::cout << std::setw(5) << j;
    std::cout << '\n';
    for (int i = 0; i < n; i++) {
        std::cout << std::setw(5) << i;
        for (int j = 1; j <= n; j++) {
            std::cout << std::setw(5) << (j <= i ? std::string(".") : names[t.root[i][j]]);
        }
        std::cout << '\n';
    }
    std::vector<int> depth(f.size(), 0);
    bst_depths(t.root, 0, n, 1, depth);
    std::cout << "  árbol: " << bst_shape(t.root, names, 0, n) << "\n  profundidades:";
    for (int i = 0; i < n; i++) std::cout << ' ' << names[i] << '=' << depth[i];
    std::cout << "\n  costo con la definición: " << cost_from_depths(f, depth) << '\n';
    std::cout << "  por fuerza bruta: " << bst_brute(f) << '\n';
    std::cout << "  la más frecuente a la raíz: " << bst_most_frequent_root(f, 0, n, 1) << '\n';
    std::cout << "  balanceado: " << bst_balanced(f, 0, n, 1) << '\n';
}

}  // namespace

int main() {
    show({"A", "B", "C", "D"}, {2, 1, 3, 4});
}
chain_bench.cppDescargar chain_bench.cpp
// Mide la tabla de la cadena de matrices. Menor de 3 intentos, con -O2.
#include <chrono>
#include <cstdio>
#include <random>
#include <vector>

#include "interval_dp.h"

int main() {
    std::puts("Cadena de matrices, dimensiones al azar entre 1 y 50, menor de 3 intentos");
    std::puts("    n   combinaciones        ms");
    double previous = 0;
    for (int n : {200, 400, 800, 1600}) {
        std::mt19937 rng(11);
        std::vector<long long> d(static_cast<std::size_t>(n) + 1);
        for (long long& x : d) x = 1 + static_cast<long long>(rng() % 50);
        double best = 1e18;
        long long combinations = 0;
        for (int attempt = 0; attempt < 3; attempt++) {
            combinations = 0;
            const auto start = std::chrono::steady_clock::now();
            matrix_chain(d, &combinations);
            const auto stop = std::chrono::steady_clock::now();
            best = std::min(best, std::chrono::duration<double, std::milli>(stop - start).count());
        }
        if (previous > 0) {
            std::printf("%5d %15lld %9.1f   x%.1f\n", n, combinations, best, best / previous);
        } else {
            std::printf("%5d %15lld %9.1f\n", n, combinations, best);
        }
        previous = best;
    }
}
chain_demo.cppDescargar chain_demo.cpp
// Cadena de matrices: la pregunta de repaso, el greedy que falla, la tabla por largo y el orden ingenuo.
#include <iomanip>
#include <iostream>
#include <vector>

#include "interval_dp.h"

namespace {

void print_dimensions(const std::vector<long long>& d) {
    for (std::size_t i = 0; i + 1 < d.size(); i++) {
        std::cout << (i ? ", " : "") << d[i] << "x" << d[i + 1];
    }
}

void print_table(const CostTable& cost) {
    const int n = static_cast<int>(cost.size());
    std::cout << "  i\\j";
    for (int j = 0; j < n; j++) std::cout << std::setw(8) << j;
    std::cout << '\n';
    for (int i = 0; i < n; i++) {
        std::cout << std::setw(5) << i;
        for (int j = 0; j < n; j++) {
            if (j < i) {
                std::cout << std::setw(8) << ".";
            } else {
                std::cout << std::setw(8) << cost[i][j];
            }
        }
        std::cout << '\n';
    }
}

void show_chain(const std::vector<long long>& d) {
    std::cout << "Matrices: ";
    print_dimensions(d);
    const ChainTables t = matrix_chain(d);
    const int last = static_cast<int>(d.size()) - 2;
    std::cout << '\n';
    print_table(t.cost);
    std::cout << "Mínimo: " << t.cost[0][last] << ", con " << parenthesize(t.split, 0, last) << '\n';
}

}  // namespace

int main() {
    const std::vector<long long> review = {10, 30, 5, 60};
    show_chain(review);
    std::cout << "  (A0 A1) A2: " << 10 * 30 * 5 + 10 * 5 * 60 << '\n';
    std::cout << "  A0 (A1 A2): " << 30 * 5 * 60 + 10 * 30 * 60 << "\n\n";

    const std::vector<long long> greedy_case = {4, 1, 3, 5};
    std::cout << "Primero la multiplicación más barata\n  matrices: ";
    print_dimensions(greedy_case);
    std::cout << "\n  greedy: " << chain_cheapest_first(greedy_case) << ", óptimo por fuerza bruta: "
              << chain_brute(greedy_case) << "\n\n";

    const std::vector<long long> d = {10, 20, 5, 30, 2};
    show_chain(d);
    std::cout << "Por fuerza bruta: " << chain_brute(d) << "\n\n";

    std::cout << "Orden ingenuo: i de menor a mayor, j de menor a mayor, con la tabla en ceros\n";
    for (const std::vector<long long>& instance : {review, d}) {
        const NaiveOrderRun run = matrix_chain_naive_order(instance);
        std::cout << "  ";
        print_dimensions(instance);
        std::cout << '\n';
        for (const std::string& line : run.first_reads) std::cout << "    " << line << '\n';
        std::cout << "    lecturas vacías: " << run.empty_reads << ", respuesta: " << run.answer << ", correcta: "
                  << matrix_chain(instance).cost[0][instance.size() - 2] << '\n';
    }

    std::cout << "\nOtro orden válido, i de mayor a menor y j de menor a mayor: " << matrix_chain_i_descending(d) << '\n';

    long long combinations = 0;
    const std::vector<long long> ten(11, 2);
    matrix_chain(ten, &combinations);
    std::cout << "Con n = 10 matrices, combinaciones (i, k, j) probadas: " << combinations << '\n';
}
dag_demo.cppDescargar dag_demo.cpp
// El grafo de subproblemas, y el camino más largo en un DAG con pesos negativos y un nodo sin alcanzar.
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

#include "dag_dp.h"

namespace {

void show_subproblem_graphs() {
    std::cout << "Fibonacci, n = 5: quién lee a quién\n";
    for (int n = 2; n <= 5; n++) {
        std::cout << "  F(" << n - 1 << ") -> F(" << n << ")   F(" << n - 2 << ") -> F(" << n << ")\n";
    }
    std::cout << "  6 nodos, 8 aristas\n\n";

    const std::string a = "casa";
    const std::string b = "cosas";
    const WeightedDag g = edit_subproblem_dag(a, b);
    std::size_t arcs = 0;
    for (const auto& out : g) arcs += out.size();
    std::cout << "Distancia de edición, " << a << " -> " << b << ": " << g.size() << " nodos, " << arcs << " aristas\n";
    const LongestPaths paths = longest_paths(g, edit_node(0, 0, b.size()));
    const int target = edit_node(a.size(), b.size(), b.size());
    std::cout << "  distancia, como camino en ese DAG: " << -paths.length[target] << '\n';
    std::cout << "  el camino, en casillas (i, j):";
    for (int v : path_to(paths, target)) {
        std::cout << " (" << v / static_cast<int>(b.size() + 1) << "," << v % static_cast<int>(b.size() + 1) << ")";
    }
    std::cout << "\n\n";

    const std::vector<KnapsackObject> objects = {{2, 3}, {3, 4}, {4, 5}, {5, 8}};
    std::cout << "Mochila de la semana 7, capacidad 9, como camino más largo: " << knapsack_as_path(objects, 9) << "\n\n";
}

void show_longest_path() {
    // El nodo 0 no se alcanza desde el origen 1, y es la cola de una arista muy pesada.
    WeightedDag g(7);
    g[0] = {{2, 100}, {6, 1}};
    g[1] = {{2, 3}, {3, 5}};
    g[2] = {{4, -1}};
    g[3] = {{4, -4}, {5, 2}};
    g[4] = {{5, 6}};

    std::cout << "DAG:";
    for (int u = 0; u < 7; u++) {
        for (const Arc& arc : g[u]) std::cout << ' ' << u << "->" << arc.to << '(' << arc.weight << ')';
    }
    std::cout << "\nOrigen: 1\nOrden topológico:";
    for (int u : topological_order(g)) std::cout << ' ' << u;
    std::cout << "\n\n";

    const int source = 1;
    const LongestPaths r = longest_paths(g, source);
    const std::vector<long long> anywhere = longest_from_anywhere(g);
    std::cout << "nodo   L(v)  pred  todos en 0\n";
    for (int v = 0; v < 7; v++) {
        std::cout << std::setw(4) << v;
        if (r.reached[v]) {
            std::cout << std::setw(7) << r.length[v] << std::setw(6) << r.pred[v];
        } else {
            std::cout << std::setw(7) << "-inf" << std::setw(6) << "-";
        }
        std::cout << std::setw(12) << anywhere[v] << '\n';
    }

    std::cout << "\nCamino más largo de 1 a 5:";
    for (int v : path_to(r, 5)) std::cout << ' ' << v;
    std::cout << ", largo " << r.length[5] << '\n';

    long long total = 0;
    std::vector<int> walk;
    heaviest_arc_walk(g, source, 5, total, walk);
    std::cout << "Salir por la arista más pesada:";
    for (int v : walk) std::cout << ' ' << v;
    std::cout << ", largo " << total << '\n';
}

}  // namespace

int main() {
    show_subproblem_graphs();
    show_longest_path();
}
dag_dp.hDescargar dag_dp.h
// Camino más largo en un DAG, y los subproblemas de la semana 7 vistos como caminos.
#pragma once

#include <algorithm>
#include <string>
#include <vector>

struct Arc {
    int to;
    long long weight;
};

using WeightedDag = std::vector<std::vector<Arc>>;

// Orden topológico por DFS, como en la semana 6: lo que termina último va primero.
inline void visit_for_order(const WeightedDag& g, int u, std::vector<char>& seen, std::vector<int>& finished) {
    seen[u] = 1;
    for (const Arc& arc : g[u]) {
        if (!seen[arc.to]) visit_for_order(g, arc.to, seen, finished);
    }
    finished.push_back(u);
}

inline std::vector<int> topological_order(const WeightedDag& g) {
    std::vector<char> seen(g.size(), 0);
    std::vector<int> finished;
    for (int u = 0; u < static_cast<int>(g.size()); u++) {
        if (!seen[u]) visit_for_order(g, u, seen, finished);
    }
    std::reverse(finished.begin(), finished.end());
    return finished;
}

struct LongestPaths {
    std::vector<long long> length;  // solo tiene sentido si reached[v]
    std::vector<char> reached;      // hay camino desde el origen
    std::vector<int> pred;          // nodo anterior en un camino más largo, o -1
};

// Invariante: al llegar a u en el orden, ya se miraron todas las aristas que entran a u, así que
// length[u] es definitivo. De un nodo no alcanzado no se sale: no hay valor que sumarle.
inline LongestPaths longest_paths(const WeightedDag& g, int source) {
    const std::size_t n = g.size();
    LongestPaths r{std::vector<long long>(n, 0), std::vector<char>(n, 0), std::vector<int>(n, -1)};
    r.reached[source] = 1;
    for (int u : topological_order(g)) {
        if (!r.reached[u]) continue;
        for (const Arc& arc : g[u]) {
            const long long candidate = r.length[u] + arc.weight;
            if (!r.reached[arc.to] || candidate > r.length[arc.to]) {
                r.reached[arc.to] = 1;
                r.length[arc.to] = candidate;
                r.pred[arc.to] = u;
            }
        }
    }
    return r;
}

inline std::vector<int> path_to(const LongestPaths& r, int target) {
    std::vector<int> path;
    for (int v = target; v != -1; v = r.pred[v]) path.push_back(v);
    std::reverse(path.begin(), path.end());
    return path;
}

// Error: todos empiezan en 0, como si cualquier nodo fuera origen.
inline std::vector<long long> longest_from_anywhere(const WeightedDag& g) {
    std::vector<long long> length(g.size(), 0);
    for (int u : topological_order(g)) {
        for (const Arc& arc : g[u]) length[arc.to] = std::max(length[arc.to], length[u] + arc.weight);
    }
    return length;
}

// Greedy que falla: salir siempre por la arista de mayor peso. Devuelve el largo del recorrido
// si llega al destino, y escribe el recorrido en walk.
inline bool heaviest_arc_walk(const WeightedDag& g, int source, int target, long long& total, std::vector<int>& walk) {
    total = 0;
    walk = {source};
    int u = source;
    while (u != target && !g[u].empty()) {
        const Arc* best = &g[u][0];
        for (const Arc& arc : g[u]) {
            if (arc.weight > best->weight) best = &arc;
        }
        total += best->weight;
        u = best->to;
        walk.push_back(u);
    }
    return u == target;
}

// Árbitro: prueba todos los caminos desde u. Máximo en best[v] para cada v alcanzable.
inline void all_paths(const WeightedDag& g, int u, long long so_far, std::vector<long long>& best,
                      std::vector<char>& seen) {
    if (!seen[u] || so_far > best[u]) {
        seen[u] = 1;
        best[u] = so_far;
    }
    for (const Arc& arc : g[u]) all_paths(g, arc.to, so_far + arc.weight, best, seen);
}

// El grafo de subproblemas de la distancia de edición: una arista por cada casilla leída, de la
// casilla leída a la que la lee. El peso es menos el costo, para usar el camino más largo.
inline int edit_node(std::size_t i, std::size_t j, std::size_t m) {
    return static_cast<int>(i * (m + 1) + j);
}

inline WeightedDag edit_subproblem_dag(const std::string& a, const std::string& b) {
    const std::size_t n = a.size();
    const std::size_t m = b.size();
    WeightedDag g((n + 1) * (m + 1));
    for (std::size_t i = 0; i <= n; i++) {
        for (std::size_t j = 0; j <= m; j++) {
            const int here = edit_node(i, j, m);
            if (i < n) g[here].push_back({edit_node(i + 1, j, m), -1});
            if (j < m) g[here].push_back({edit_node(i, j + 1, m), -1});
            if (i < n && j < m) g[here].push_back({edit_node(i + 1, j + 1, m), a[i] == b[j] ? 0 : -1});
        }
    }
    return g;
}

// El de la mochila: un nodo por (i, c), más un origen que da a todas las casillas (0, c).
struct KnapsackObject {
    std::size_t weight;
    long long value;
};

inline long long knapsack_as_path(const std::vector<KnapsackObject>& objects, std::size_t capacity) {
    const std::size_t rows = objects.size() + 1;
    const std::size_t columns = capacity + 1;
    const auto node = [columns](std::size_t i, std::size_t c) { return static_cast<int>(i * columns + c); };
    const int source = static_cast<int>(rows * columns);
    WeightedDag g(rows * columns + 1);
    for (std::size_t c = 0; c < columns; c++) g[source].push_back({node(0, c), 0});
    for (std::size_t i = 1; i < rows; i++) {
        const KnapsackObject& object = objects[i - 1];
        for (std::size_t c = 0; c < columns; c++) {
            g[node(i - 1, c)].push_back({node(i, c), 0});
            if (object.weight <= c) g[node(i - 1, c - object.weight)].push_back({node(i, c), object.value});
        }
    }
    return longest_paths(g, source).length[node(rows - 1, capacity)];
}
deep_chain.cppDescargar deep_chain.cpp
// Una cadena de n nodos, primero sin recursión y luego con ella: deep_chain N. Se compila con -O2.
#include <cstdlib>
#include <iostream>
#include <vector>

#include "tree_dp.h"

int main(int argc, char* argv[]) {
    const int n = argc > 1 ? std::atoi(argv[1]) : 100000;
    RootedTree chain{std::vector<std::vector<int>>(static_cast<std::size_t>(n)), 0,
                     std::vector<long long>(static_cast<std::size_t>(n), 1)};
    for (int v = 0; v + 1 < n; v++) chain.children[v].push_back(v + 1);
    std::cout << "orden inverso de BFS, nodos bajo la raíz: " << subtree_sizes_bfs_order(chain.children, 0)[0] << std::endl;
    std::cout << "recursión, conjunto independiente: " << independent_set_value(chain) << std::endl;
}
interval_dp.hDescargar interval_dp.h
// Programación dinámica sobre intervalos: cadena de matrices y árbol binario de búsqueda óptimo.
#pragma once

#include <algorithm>
#include <cstddef>
#include <limits>
#include <string>
#include <vector>

using CostTable = std::vector<std::vector<long long>>;
using SplitTable = std::vector<std::vector<int>>;

constexpr long long NO_COST = std::numeric_limits<long long>::max();

// ---------- Cadena de matrices. La matriz i mide d[i] x d[i + 1]. ----------

struct ChainTables {
    CostTable cost;    // cost[i][j]: el mínimo para multiplicar las matrices i..j, ambas incluidas
    SplitTable split;  // split[i][j]: la k de la última multiplicación
};

inline long long merge_cost(const std::vector<long long>& d, int i, int k, int j) {
    return d[i] * d[k + 1] * d[j + 1];
}

// Por largo creciente: todo lo que se lee es más corto que lo que se escribe.
inline ChainTables matrix_chain(const std::vector<long long>& d, long long* combinations = nullptr) {
    const int n = static_cast<int>(d.size()) - 1;
    ChainTables t{CostTable(n, std::vector<long long>(n, 0)), SplitTable(n, std::vector<int>(n, -1))};
    for (int length = 2; length <= n; length++) {
        for (int i = 0; i + length - 1 < n; i++) {
            const int j = i + length - 1;
            t.cost[i][j] = NO_COST;
            for (int k = i; k < j; k++) {
                if (combinations) (*combinations)++;
                const long long c = t.cost[i][k] + t.cost[k + 1][j] + merge_cost(d, i, k, j);
                if (c < t.cost[i][j]) {
                    t.cost[i][j] = c;
                    t.split[i][j] = k;
                }
            }
        }
    }
    return t;
}

// También sirve: i de mayor a menor y j de menor a mayor. Lo leído tiene i mayor, o j menor.
inline long long matrix_chain_i_descending(const std::vector<long long>& d) {
    const int n = static_cast<int>(d.size()) - 1;
    CostTable cost(n, std::vector<long long>(n, 0));
    for (int i = n - 1; i >= 0; i--) {
        for (int j = i + 1; j < n; j++) {
            cost[i][j] = NO_COST;
            for (int k = i; k < j; k++) {
                cost[i][j] = std::min(cost[i][j], cost[i][k] + cost[k + 1][j] + merge_cost(d, i, k, j));
            }
        }
    }
    return n == 0 ? 0 : cost[0][n - 1];
}

// Error: i de menor a mayor y j de menor a mayor. Las casillas sin llenar valen 0 y se leen igual.
struct NaiveOrderRun {
    long long answer;
    long long empty_reads;
    std::vector<std::string> first_reads;
};

inline NaiveOrderRun matrix_chain_naive_order(const std::vector<long long>& d) {
    const int n = static_cast<int>(d.size()) - 1;
    CostTable cost(n, std::vector<long long>(n, 0));
    std::vector<std::vector<char>> filled(n, std::vector<char>(n, 0));
    for (int i = 0; i < n; i++) filled[i][i] = 1;
    NaiveOrderRun run{0, 0, {}};
    const auto read = [&](int writing_i, int writing_j, int i, int j) {
        if (!filled[i][j]) {
            run.empty_reads++;
            run.first_reads.push_back("M[" + std::to_string(writing_i) + "][" + std::to_string(writing_j) +
                                      "] lee M[" + std::to_string(i) + "][" + std::to_string(j) + "], vacía");
        }
        return cost[i][j];
    };
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            long long best = NO_COST;
            for (int k = i; k < j; k++) {
                best = std::min(best, read(i, j, i, k) + read(i, j, k + 1, j) + merge_cost(d, i, k, j));
            }
            cost[i][j] = best;
            filled[i][j] = 1;
        }
    }
    run.answer = n == 0 ? 0 : cost[0][n - 1];
    return run;
}

inline std::string parenthesize(const SplitTable& split, int i, int j) {
    if (i == j) return "A" + std::to_string(i);
    const int k = split[i][j];
    return "(" + parenthesize(split, i, k) + " " + parenthesize(split, k + 1, j) + ")";
}

// Árbitro: arma cada parentización y calcula su costo multiplicando de verdad las dimensiones.
struct ChainPlan {
    long long rows;
    long long columns;
    long long cost;
};

inline std::vector<ChainPlan> all_chain_plans(const std::vector<long long>& d, int i, int j) {
    if (i == j) return {{d[i], d[i + 1], 0}};
    std::vector<ChainPlan> plans;
    for (int k = i; k < j; k++) {
        for (const ChainPlan& left : all_chain_plans(d, i, k)) {
            for (const ChainPlan& right : all_chain_plans(d, k + 1, j)) {
                plans.push_back({left.rows, right.columns,
                                 left.cost + right.cost + left.rows * left.columns * right.columns});
            }
        }
    }
    return plans;
}

inline long long chain_brute(const std::vector<long long>& d) {
    long long best = NO_COST;
    for (const ChainPlan& plan : all_chain_plans(d, 0, static_cast<int>(d.size()) - 2)) {
        best = std::min(best, plan.cost);
    }
    return best;
}

// Greedy que falla: hacer siempre primero la multiplicación vecina más barata.
inline long long chain_cheapest_first(std::vector<long long> d) {
    long long total = 0;
    while (d.size() > 2) {
        std::size_t best = 1;
        for (std::size_t m = 1; m + 1 < d.size(); m++) {
            if (d[m - 1] * d[m] * d[m + 1] < d[best - 1] * d[best] * d[best + 1]) best = m;
        }
        total += d[best - 1] * d[best] * d[best + 1];
        d.erase(d.begin() + static_cast<std::ptrdiff_t>(best));
    }
    return total;
}

// ---------- Árbol binario de búsqueda óptimo. Rangos semiabiertos [i, j). ----------

struct BstTables {
    CostTable cost;  // cost[i][j]: el mínimo de la suma de frecuencia por profundidad, claves i..j-1
    SplitTable root; // root[i][j]: la clave que va en la raíz de ese rango
};

inline std::vector<long long> prefix_sums(const std::vector<long long>& f) {
    std::vector<long long> sum(f.size() + 1, 0);
    for (std::size_t i = 0; i < f.size(); i++) sum[i + 1] = sum[i] + f[i];
    return sum;
}

inline BstTables optimal_bst(const std::vector<long long>& f) {
    const int n = static_cast<int>(f.size());
    const std::vector<long long> sum = prefix_sums(f);
    BstTables t{CostTable(n + 1, std::vector<long long>(n + 1, 0)), SplitTable(n + 1, std::vector<int>(n + 1, -1))};
    for (int length = 1; length <= n; length++) {
        for (int i = 0; i + length <= n; i++) {
            const int j = i + length;
            long long best = NO_COST;
            for (int r = i; r < j; r++) {
                const long long c = t.cost[i][r] + t.cost[r + 1][j];
                if (c < best) {
                    best = c;
                    t.root[i][j] = r;
                }
            }
            t.cost[i][j] = best + sum[j] - sum[i];
        }
    }
    return t;
}

// Las profundidades de cada clave en el árbol que dice la tabla. La raíz tiene profundidad 1.
inline void bst_depths(const SplitTable& root, int i, int j, int depth, std::vector<int>& out) {
    if (i >= j) return;
    const int r = root[i][j];
    out[r] = depth;
    bst_depths(root, i, r, depth + 1, out);
    bst_depths(root, r + 1, j, depth + 1, out);
}

inline long long cost_from_depths(const std::vector<long long>& f, const std::vector<int>& depth) {
    long long total = 0;
    for (std::size_t i = 0; i < f.size(); i++) total += f[i] * depth[i];
    return total;
}

inline std::string bst_shape(const SplitTable& root, const std::vector<std::string>& names, int i, int j) {
    if (i >= j) return "-";
    const int r = root[i][j];
    if (j - i == 1) return names[r];
    return names[r] + "(" + bst_shape(root, names, i, r) + " " + bst_shape(root, names, r + 1, j) + ")";
}

// Árbitro: genera todos los árboles del rango como listas de profundidades, y mide con la definición.
inline std::vector<std::vector<int>> all_bst_depths(int i, int j, int n) {
    if (i >= j) return {std::vector<int>(n, 0)};
    std::vector<std::vector<int>> trees;
    for (int r = i; r < j; r++) {
        for (const std::vector<int>& left : all_bst_depths(i, r, n)) {
            for (const std::vector<int>& right : all_bst_depths(r + 1, j, n)) {
                std::vector<int> depth(n, 0);
                depth[r] = 1;
                for (int x = i; x < r; x++) depth[x] = left[x] + 1;
                for (int x = r + 1; x < j; x++) depth[x] = right[x] + 1;
                trees.push_back(depth);
            }
        }
    }
    return trees;
}

inline long long bst_brute(const std::vector<long long>& f) {
    const int n = static_cast<int>(f.size());
    long long best = NO_COST;
    for (const std::vector<int>& depth : all_bst_depths(0, n, n)) {
        best = std::min(best, cost_from_depths(f, depth));
    }
    return best;
}

// Greedy que falla: la clave más frecuente a la raíz, y lo mismo en cada lado.
inline long long bst_most_frequent_root(const std::vector<long long>& f, int i, int j, int depth) {
    if (i >= j) return 0;
    int r = i;
    for (int x = i; x < j; x++) {
        if (f[x] > f[r]) r = x;
    }
    return f[r] * depth + bst_most_frequent_root(f, i, r, depth + 1) + bst_most_frequent_root(f, r + 1, j, depth + 1);
}

// La otra idea natural: el árbol balanceado, ignorando las frecuencias.
inline long long bst_balanced(const std::vector<long long>& f, int i, int j, int depth) {
    if (i >= j) return 0;
    const int r = i + (j - i) / 2;
    return f[r] * depth + bst_balanced(f, i, r, depth + 1) + bst_balanced(f, r + 1, j, depth + 1);
}
lis_bench.cppDescargar lis_bench.cpp
// Mide las dos versiones de la subsecuencia creciente más larga. Menor de 3 intentos, con -O2.
#include <chrono>
#include <cstdio>
#include <random>
#include <vector>

#include "lis.h"

namespace {

std::vector<int> random_values(std::size_t n, unsigned seed) {
    std::mt19937 rng(seed);
    std::vector<int> a(n);
    for (int& x : a) x = static_cast<int>(rng() >> 2);
    return a;
}

template <typename Function>
double best_ms(Function run) {
    double best = 1e18;
    for (int attempt = 0; attempt < 3; attempt++) {
        const auto start = std::chrono::steady_clock::now();
        run();
        const auto stop = std::chrono::steady_clock::now();
        best = std::min(best, std::chrono::duration<double, std::milli>(stop - start).count());
    }
    return best;
}

}  // namespace

int main() {
    std::puts("Subsecuencia creciente más larga, valores al azar, menor de 3 intentos");
    std::puts("        n   largo   tabla ms   finales ms");
    for (std::size_t n : {10000u, 20000u, 40000u, 80000u, 1000000u, 2000000u, 4000000u}) {
        const std::vector<int> a = random_values(n, 9);
        std::size_t length = 0;
        const double fast = best_ms([&] { length = lis_length_tails(a); });
        if (n <= 80000u) {
            int slow_length = 0;
            const double slow = best_ms([&] { slow_length = lis_length_quadratic(a); });
            if (slow_length != static_cast<int>(length)) return 1;
            std::printf("%9zu %7zu %10.1f %12.1f\n", n, length, slow, fast);
        } else {
            std::printf("%9zu %7zu %10s %12.1f\n", n, length, "-", fast);
        }
    }
}
lis_demo.cppDescargar lis_demo.cpp
// Subsecuencia creciente más larga: la tabla, el arreglo de finales paso a paso y la reconstrucción.
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

#include "lis.h"

namespace {

void print_values(const std::vector<int>& a) {
    for (std::size_t i = 0; i < a.size(); i++) std::cout << (i ? " " : "") << a[i];
}

void show_quadratic(const std::vector<int>& a) {
    const LisTable t = lis_quadratic(a);
    std::cout << "   i:";
    for (std::size_t i = 0; i < a.size(); i++) std::cout << std::setw(3) << i;
    std::cout << "\na[i]:";
    for (int x : a) std::cout << std::setw(3) << x;
    std::cout << "\nE[i]:";
    for (int e : t.ending) std::cout << std::setw(3) << e;
    std::cout << "\npred:";
    for (int p : t.pred) std::cout << std::setw(3) << p;
    std::cout << '\n';
}

void show_tails(const std::vector<int>& a) {
    std::vector<int> tails;
    std::vector<int> tail_index;
    std::vector<int> pred(a.size(), -1);
    std::cout << " i  x  posición  finales después        índices    pred[i]\n";
    for (int i = 0; i < static_cast<int>(a.size()); i++) {
        const auto it = std::lower_bound(tails.begin(), tails.end(), a[i]);
        const std::size_t position = static_cast<std::size_t>(it - tails.begin());
        if (position > 0) pred[i] = tail_index[position - 1];
        if (it == tails.end()) {
            tails.push_back(a[i]);
            tail_index.push_back(i);
        } else {
            *it = a[i];
            tail_index[position] = i;
        }
        std::cout << std::setw(2) << i << std::setw(3) << a[i] << std::setw(10) << position << "  ";
        std::string text;
        for (int x : tails) text += std::to_string(x) + " ";
        std::cout << std::left << std::setw(23) << text;
        text.clear();
        for (int x : tail_index) text += std::to_string(x) + " ";
        std::cout << std::setw(11) << text << std::right << std::setw(4) << pred[i] << '\n';
    }
}

}  // namespace

int main() {
    const std::vector<int> a = {3, 1, 4, 1, 5, 9, 2, 6};
    std::cout << "Arreglo: ";
    print_values(a);
    std::cout << "\n\nLa tabla cuadrática\n";
    show_quadratic(a);
    std::cout << "Largo: " << lis_length_quadratic(a) << "\nCon pred, desde la primera casilla con ese largo:";
    {
        const LisTable t = lis_quadratic(a);
        std::vector<int> walk;
        for (int i = static_cast<int>(std::max_element(t.ending.begin(), t.ending.end()) - t.ending.begin()); i != -1; i = t.pred[i]) {
            walk.push_back(a[i]);
        }
        for (std::size_t k = walk.size(); k-- > 0;) std::cout << ' ' << walk[k];
    }
    std::cout << "\n\nEl arreglo de finales, con lower_bound\n";
    show_tails(a);
    std::cout << "Largo: " << lis_length_tails(a) << "\nReconstruida:";
    for (int i : lis_indices(a)) std::cout << ' ' << a[i];

    std::cout << "\n\nTomar todo lo que sea mayor que lo último tomado\n";
    for (const std::vector<int>& b : {std::vector<int>{5, 1, 2, 3}, std::vector<int>{1, 7, 2, 3, 4}}) {
        std::cout << "  ";
        print_values(b);
        std::cout << ": greedy " << lis_greedy_take(b) << ", óptimo " << lis_length_tails(b) << '\n';
    }

    const std::vector<int> c = {2, 5, 3, 7, 1};
    std::cout << "\nLos finales no son una subsecuencia\n  arreglo: ";
    print_values(c);
    std::cout << "\n  finales: ";
    print_values(final_tails(c));
    std::cout << "\n  reconstruida:";
    for (int i : lis_indices(c)) std::cout << ' ' << c[i];
    std::cout << '\n';

    std::cout << "\nlower_bound y upper_bound\n";
    for (const std::vector<int>& d : {std::vector<int>{2, 2, 2}, std::vector<int>{1, 3, 3, 5}}) {
        std::cout << "  ";
        print_values(d);
        std::cout << ": con lower_bound " << lis_length_tails(d) << ", con upper_bound "
                  << non_decreasing_length_tails(d) << '\n';
    }
}
lis.hDescargar lis.h
// Subsecuencia creciente más larga: estrictamente creciente, salvo donde se dice otra cosa.
#pragma once

#include <algorithm>
#include <cstddef>
#include <vector>

struct LisTable {
    std::vector<int> ending;  // ending[i]: el largo de la más larga que termina justo en a[i]
    std::vector<int> pred;    // índice del elemento anterior en esa subsecuencia, o -1
};

inline LisTable lis_quadratic(const std::vector<int>& a) {
    const int n = static_cast<int>(a.size());
    LisTable t{std::vector<int>(a.size(), 1), std::vector<int>(a.size(), -1)};
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (a[j] < a[i] && t.ending[j] + 1 > t.ending[i]) {
                t.ending[i] = t.ending[j] + 1;
                t.pred[i] = j;
            }
        }
    }
    return t;
}

inline int lis_length_quadratic(const std::vector<int>& a) {
    const LisTable t = lis_quadratic(a);
    return t.ending.empty() ? 0 : *std::max_element(t.ending.begin(), t.ending.end());
}

// Invariante: tails es estrictamente creciente, y tails[k] es el menor último valor posible de una
// subsecuencia creciente de largo k + 1 entre los elementos ya vistos.
inline std::size_t lis_length_tails(const std::vector<int>& a) {
    std::vector<int> tails;
    for (int x : a) {
        const auto it = std::lower_bound(tails.begin(), tails.end(), x);
        if (it == tails.end()) {
            tails.push_back(x);
        } else {
            *it = x;
        }
    }
    return tails.size();
}

// Con upper_bound se calcula otra cosa: la más larga que no decrece.
inline std::size_t non_decreasing_length_tails(const std::vector<int>& a) {
    std::vector<int> tails;
    for (int x : a) {
        const auto it = std::upper_bound(tails.begin(), tails.end(), x);
        if (it == tails.end()) {
            tails.push_back(x);
        } else {
            *it = x;
        }
    }
    return tails.size();
}

inline std::vector<int> final_tails(const std::vector<int>& a) {
    std::vector<int> tails;
    for (int x : a) {
        const auto it = std::lower_bound(tails.begin(), tails.end(), x);
        if (it == tails.end()) {
            tails.push_back(x);
        } else {
            *it = x;
        }
    }
    return tails;
}

// Con reconstrucción: tail_index[k] es el índice de tails[k] en a, y pred[i] se fija cuando entra a[i].
inline std::vector<int> lis_indices(const std::vector<int>& a) {
    std::vector<int> tails;
    std::vector<int> tail_index;
    std::vector<int> pred(a.size(), -1);
    for (int i = 0; i < static_cast<int>(a.size()); i++) {
        const auto it = std::lower_bound(tails.begin(), tails.end(), a[i]);
        const std::size_t position = static_cast<std::size_t>(it - tails.begin());
        if (position > 0) pred[i] = tail_index[position - 1];
        if (it == tails.end()) {
            tails.push_back(a[i]);
            tail_index.push_back(i);
        } else {
            *it = a[i];
            tail_index[position] = i;
        }
    }
    std::vector<int> indices;
    if (!tail_index.empty()) {
        for (int i = tail_index.back(); i != -1; i = pred[i]) indices.push_back(i);
    }
    std::reverse(indices.begin(), indices.end());
    return indices;
}

// Greedy que falla: recorrer tomando todo lo que sea mayor que lo último tomado.
inline int lis_greedy_take(const std::vector<int>& a) {
    int taken = 0;
    int last = 0;
    for (int x : a) {
        if (taken == 0 || x > last) {
            taken++;
            last = x;
        }
    }
    return taken;
}

// Árbitro: prueba los 2^n subconjuntos de posiciones.
inline int lis_brute(const std::vector<int>& a, bool strict = true) {
    const std::size_t n = a.size();
    int best = 0;
    for (unsigned mask = 0; mask < (1u << n); mask++) {
        int count = 0;
        int last = 0;
        bool ok = true;
        for (std::size_t i = 0; i < n && ok; i++) {
            if (!(mask >> i & 1u)) continue;
            if (count > 0 && (strict ? a[i] <= last : a[i] < last)) ok = false;
            last = a[i];
            count++;
        }
        if (ok) best = std::max(best, count);
    }
    return best;
}
tree_demo.cppDescargar tree_demo.cpp
// Conjunto independiente y cobertura en un árbol: dos estados, los errores y lo que pasa con un ciclo.
#include <iomanip>
#include <iostream>
#include <vector>

#include "tree_dp.h"

namespace {

void print_nodes(const std::vector<int>& nodes) {
    std::cout << '{';
    for (std::size_t i = 0; i < nodes.size(); i++) std::cout << (i ? ", " : "") << nodes[i];
    std::cout << '}';
}

void compare(const char* title, const RootedTree& tree) {
    std::cout << title << '\n';
    std::cout << "  fuerza bruta:              " << independent_set_brute(tree) << '\n';
    std::cout << "  dos estados:               " << independent_set_value(tree) << '\n';
    std::cout << "  niveles alternos:          " << alternate_levels(tree) << '\n';
    std::cout << "  mayor valor primero:       " << largest_value_first(tree) << '\n';
    std::cout << "  sin elegir obliga a hijos: " << forced_children_value(tree) << '\n';
}

}  // namespace

int main() {
    const RootedTree company{{{1, 2}, {3, 4}, {5}, {}, {}, {6, 7}, {}, {}}, 0, {4, 9, 6, 5, 5, 8, 3, 4}};
    std::cout << "Árbol: padre -> hijos\n";
    for (std::size_t v = 0; v < company.children.size(); v++) {
        std::cout << "  " << v << " (valor " << company.value[v] << ") ->";
        for (int c : company.children[v]) std::cout << ' ' << c;
        std::cout << '\n';
    }

    const TwoStates s = independent_set_states(company);
    std::cout << "\nnodo  valor   con  sin\n";
    for (std::size_t t = company.value.size(); t-- > 0;) {
        std::cout << std::setw(4) << t << std::setw(7) << company.value[t] << std::setw(6) << s.with_node[t]
                  << std::setw(5) << s.without_node[t] << '\n';
    }
    std::cout << "Mejor valor: " << independent_set_value(company) << ", con ";
    print_nodes(independent_set_nodes(company));
    std::cout << "\nCada nodo por separado, sin mirar al padre: ";
    print_nodes(choose_each_node_alone(company));
    std::cout << "\n\n";

    compare("El árbol de arriba:", company);
    const RootedTree chain{{{1}, {2}, {3}, {}}, 0, {10, 1, 1, 10}};
    compare("\nLa cadena 10, 1, 1, 10:", chain);
    const RootedTree star{{{1, 2, 3}, {}, {}, {}}, 0, {5, 2, 2, 2}};
    compare("\nUn jefe de valor 5 con tres personas de valor 2:", star);

    const RootedTree negative{{{1, 2}, {}, {}}, 0, {-4, -1, -2}};
    compare("\nTodos negativos:", negative);
    std::cout << "  elegidos: ";
    print_nodes(independent_set_nodes(negative));
    std::cout << '\n';

    long long total = 0;
    for (long long w : company.value) total += w;
    std::cout << "\nCobertura de peso mínimo del primer árbol\n";
    std::cout << "  con su propia recurrencia:         " << min_cover_value(company) << '\n';
    std::cout << "  fuerza bruta:                      " << min_cover_brute(company) << '\n';
    std::cout << "  total " << total << " menos conjunto independiente: " << total - independent_set_value(company) << '\n';

    // Un triángulo: el árbol de BFS desde 0 pierde la arista 1-2, y la cuenta ya no sirve.
    const RootedTree bfs_tree_of_triangle{{{1, 2}, {}, {}}, 0, {5, 5, 5}};
    std::cout << "\nUn triángulo con valores 5, 5, 5, usando el árbol de BFS desde 0\n";
    std::cout << "  el programa de árboles dice: " << independent_set_value(bfs_tree_of_triangle) << ", con ";
    print_nodes(independent_set_nodes(bfs_tree_of_triangle));
    std::cout << "\n  pero 1 y 2 son vecinos en el triángulo, y lo mejor de verdad es 5\n";
}
tree_dp.hDescargar tree_dp.h
// Programación dinámica sobre árboles: conjunto independiente y cobertura de vértices con pesos.
#pragma once

#include <algorithm>
#include <cstddef>
#include <utility>
#include <vector>

// Un árbol con raíz, dado por las listas de hijos. Los ejemplos son chicos: aquí se usa recursión.
struct RootedTree {
    std::vector<std::vector<int>> children;
    int root;
    std::vector<long long> value;
};

struct TwoStates {
    std::vector<long long> with_node;     // lo mejor en el subárbol de v, con v dentro
    std::vector<long long> without_node;  // lo mejor en el subárbol de v, con v fuera
};

// Primero los hijos. Un nodo elegido obliga a sus hijos a quedar fuera; uno sin elegir los deja libres.
inline void fill_states(const RootedTree& tree, int v, TwoStates& s) {
    s.with_node[v] = tree.value[v];
    s.without_node[v] = 0;
    for (int c : tree.children[v]) {
        fill_states(tree, c, s);
        s.with_node[v] += s.without_node[c];
        s.without_node[v] += std::max(s.with_node[c], s.without_node[c]);
    }
}

inline TwoStates independent_set_states(const RootedTree& tree) {
    TwoStates s{std::vector<long long>(tree.value.size(), 0), std::vector<long long>(tree.value.size(), 0)};
    fill_states(tree, tree.root, s);
    return s;
}

inline long long independent_set_value(const RootedTree& tree) {
    const TwoStates s = independent_set_states(tree);
    return std::max(s.with_node[tree.root], s.without_node[tree.root]);
}

// De arriba hacia abajo: lo que se decide en un nodo depende de lo que se decidió en su padre.
inline void choose_down(const RootedTree& tree, const TwoStates& s, int v, bool parent_chosen, std::vector<int>& out) {
    const bool chosen = !parent_chosen && s.with_node[v] > s.without_node[v];
    if (chosen) out.push_back(v);
    for (int c : tree.children[v]) choose_down(tree, s, c, chosen, out);
}

inline std::vector<int> independent_set_nodes(const RootedTree& tree) {
    const TwoStates s = independent_set_states(tree);
    std::vector<int> out;
    choose_down(tree, s, tree.root, false, out);
    std::sort(out.begin(), out.end());
    return out;
}

// Error: mirar cada nodo por separado, sin preguntar qué se decidió en el padre.
inline std::vector<int> choose_each_node_alone(const RootedTree& tree) {
    const TwoStates s = independent_set_states(tree);
    std::vector<int> out;
    for (int v = 0; v < static_cast<int>(tree.value.size()); v++) {
        if (s.with_node[v] > s.without_node[v]) out.push_back(v);
    }
    return out;
}

// Error: un nodo sin elegir obliga a elegir a sus hijos.
inline std::pair<long long, long long> forced_children(const RootedTree& tree, int v) {
    long long with_v = tree.value[v];
    long long without_v = 0;
    for (int c : tree.children[v]) {
        const auto [with_c, without_c] = forced_children(tree, c);
        with_v += without_c;
        without_v += with_c;
    }
    return {with_v, without_v};
}

inline long long forced_children_value(const RootedTree& tree) {
    const auto [with_root, without_root] = forced_children(tree, tree.root);
    return std::max(with_root, without_root);
}

// Greedy que falla: los niveles pares o los impares, lo que sume más.
inline long long alternate_levels(const RootedTree& tree) {
    long long sum[2] = {0, 0};
    std::vector<std::pair<int, int>> stack = {{tree.root, 0}};
    while (!stack.empty()) {
        const auto [v, level] = stack.back();
        stack.pop_back();
        sum[level] += tree.value[v];
        for (int c : tree.children[v]) stack.push_back({c, 1 - level});
    }
    return std::max(sum[0], sum[1]);
}

// Greedy que falla: el de mayor valor primero, mientras no choque con un vecino ya elegido.
inline long long largest_value_first(const RootedTree& tree) {
    const int n = static_cast<int>(tree.value.size());
    std::vector<int> parent(tree.value.size(), -1);
    for (int v = 0; v < n; v++) {
        for (int c : tree.children[v]) parent[c] = v;
    }
    std::vector<int> order(tree.value.size());
    for (int v = 0; v < n; v++) order[v] = v;
    std::stable_sort(order.begin(), order.end(), [&tree](int x, int y) { return tree.value[x] > tree.value[y]; });
    std::vector<char> chosen(tree.value.size(), 0);
    long long total = 0;
    for (int v : order) {
        if (tree.value[v] <= 0) break;
        bool blocked = parent[v] != -1 && chosen[parent[v]];
        for (int c : tree.children[v]) blocked = blocked || chosen[c];
        if (!blocked) {
            chosen[v] = 1;
            total += tree.value[v];
        }
    }
    return total;
}

// Árbitro para árboles chicos: prueba los 2^n subconjuntos.
inline long long independent_set_brute(const RootedTree& tree) {
    const std::size_t n = tree.value.size();
    long long best = 0;
    for (unsigned mask = 0; mask < (1u << n); mask++) {
        bool ok = true;
        long long total = 0;
        for (std::size_t v = 0; v < n && ok; v++) {
            if (!(mask >> v & 1u)) continue;
            total += tree.value[v];
            for (int c : tree.children[v]) ok = ok && !(mask >> c & 1u);
        }
        if (ok) best = std::max(best, total);
    }
    return best;
}

inline long long min_cover_brute(const RootedTree& tree) {
    const std::size_t n = tree.value.size();
    long long best = 0;
    bool any = false;
    for (unsigned mask = 0; mask < (1u << n); mask++) {
        bool ok = true;
        long long total = 0;
        for (std::size_t v = 0; v < n && ok; v++) {
            if (mask >> v & 1u) total += tree.value[v];
            for (int c : tree.children[v]) ok = ok && ((mask >> v & 1u) || (mask >> c & 1u));
        }
        if (ok && (!any || total < best)) {
            best = total;
            any = true;
        }
    }
    return best;
}

// Cobertura de vértices de peso mínimo. El mismo patrón: un nodo fuera obliga a sus hijos a estar dentro.
inline std::pair<long long, long long> cover_states(const RootedTree& tree, int v) {
    long long inside = tree.value[v];
    long long outside = 0;
    for (int c : tree.children[v]) {
        const auto [inside_c, outside_c] = cover_states(tree, c);
        inside += std::min(inside_c, outside_c);
        outside += inside_c;
    }
    return {inside, outside};
}

inline long long min_cover_value(const RootedTree& tree) {
    const auto [inside, outside] = cover_states(tree, tree.root);
    return std::min(inside, outside);
}

// El orden inverso de BFS, en el cálculo más simple sobre un árbol: cuántos nodos tiene cada subárbol.
// Un padre sale siempre antes que sus hijos en un BFS, así que al revés cada nodo llega con sus hijos
// ya resueltos. No usa recursión, así que la profundidad del árbol no importa.
inline std::vector<int> subtree_sizes_bfs_order(const std::vector<std::vector<int>>& children, int root) {
    std::vector<int> parent(children.size(), -1);
    std::vector<int> order = {root};
    for (std::size_t t = 0; t < order.size(); t++) {
        for (int c : children[order[t]]) {
            parent[c] = order[t];
            order.push_back(c);
        }
    }
    std::vector<int> size(children.size(), 1);
    for (std::size_t t = order.size(); t-- > 1;) {
        size[parent[order[t]]] += size[order[t]];
    }
    return size;
}

Pruebas

Comprueban lo que afirman los apuntes; se ejecutan con make test.

test_examples.cppDescargar test_examples.cpp
// Comprueba lo que afirman los apuntes. Se compila con sanitizers.
#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <random>
#include <string>
#include <vector>

#include "dag_dp.h"
#include "interval_dp.h"
#include "lis.h"
#include "tree_dp.h"

namespace {

// Distancia de edición con la tabla de la semana 7, para compararla con el camino en el DAG.
int edit_table_value(const std::string& a, const std::string& b) {
    std::vector<std::vector<int>> d(a.size() + 1, std::vector<int>(b.size() + 1, 0));
    for (std::size_t i = 0; i <= a.size(); i++) d[i][0] = static_cast<int>(i);
    for (std::size_t j = 0; j <= b.size(); j++) d[0][j] = static_cast<int>(j);
    for (std::size_t i = 1; i <= a.size(); i++) {
        for (std::size_t j = 1; j <= b.size(); j++) {
            d[i][j] = std::min({d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1)});
        }
    }
    return d[a.size()][b.size()];
}

long long knapsack_brute_small(const std::vector<KnapsackObject>& objects, std::size_t capacity) {
    long long best = 0;
    for (unsigned mask = 0; mask < (1u << objects.size()); mask++) {
        std::size_t weight = 0;
        long long value = 0;
        for (std::size_t i = 0; i < objects.size(); i++) {
            if (mask >> i & 1u) {
                weight += objects[i].weight;
                value += objects[i].value;
            }
        }
        if (weight <= capacity) best = std::max(best, value);
    }
    return best;
}

WeightedDag random_dag(std::mt19937& rng, int n) {
    // Etiquetas barajadas: el orden del DAG no coincide con el de los números.
    std::vector<int> label(static_cast<std::size_t>(n));
    std::iota(label.begin(), label.end(), 0);
    std::shuffle(label.begin(), label.end(), rng);
    WeightedDag g(static_cast<std::size_t>(n));
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (rng() % 3 == 0) g[label[i]].push_back({label[j], static_cast<long long>(rng() % 21) - 10});
        }
    }
    return g;
}

void test_longest_path() {
    std::mt19937 rng(91);
    for (int round = 0; round < 3000; round++) {
        const int n = 1 + static_cast<int>(rng() % 9);
        const WeightedDag g = random_dag(rng, n);
        const int source = static_cast<int>(rng() % static_cast<unsigned>(n));

        const std::vector<int> order = topological_order(g);
        std::vector<int> position(static_cast<std::size_t>(n));
        for (int t = 0; t < n; t++) position[order[t]] = t;
        for (int u = 0; u < n; u++) {
            for (const Arc& arc : g[u]) assert(position[u] < position[arc.to]);
        }

        std::vector<long long> best(static_cast<std::size_t>(n), 0);
        std::vector<char> seen(static_cast<std::size_t>(n), 0);
        all_paths(g, source, 0, best, seen);
        const LongestPaths r = longest_paths(g, source);
        for (int v = 0; v < n; v++) {
            assert(r.reached[v] == seen[v]);
            if (!seen[v]) continue;
            assert(r.length[v] == best[v]);
            // El camino reconstruido empieza en el origen, usa aristas del grafo y suma ese largo.
            const std::vector<int> path = path_to(r, v);
            assert(path.front() == source && path.back() == v);
            long long total = 0;
            for (std::size_t t = 0; t + 1 < path.size(); t++) {
                long long arc_best = -1000000;
                for (const Arc& arc : g[path[t]]) {
                    if (arc.to == path[t + 1]) arc_best = std::max(arc_best, arc.weight);
                }
                assert(arc_best != -1000000);
                total += arc_best;
            }
            assert(total == best[v]);
        }
    }
}

void test_longest_path_example() {
    WeightedDag g(7);
    g[0] = {{2, 100}, {6, 1}};
    g[1] = {{2, 3}, {3, 5}};
    g[2] = {{4, -1}};
    g[3] = {{4, -4}, {5, 2}};
    g[4] = {{5, 6}};
    const LongestPaths r = longest_paths(g, 1);
    assert(!r.reached[0] && !r.reached[6]);
    assert(r.length[5] == 8);
    assert(path_to(r, 5) == (std::vector<int>{1, 2, 4, 5}));
    assert(longest_from_anywhere(g)[5] > r.length[5]);  // sin -inf, el nodo 0 contamina
    long long total = 0;
    std::vector<int> walk;
    assert(heaviest_arc_walk(g, 1, 5, total, walk));
    assert(total == 7);
}

void test_subproblem_paths() {
    std::mt19937 rng(92);
    for (int round = 0; round < 1000; round++) {
        std::string a(rng() % 7, 'a');
        std::string b(rng() % 7, 'a');
        for (char& c : a) c = static_cast<char>('a' + rng() % 3);
        for (char& c : b) c = static_cast<char>('a' + rng() % 3);
        const WeightedDag g = edit_subproblem_dag(a, b);
        const LongestPaths r = longest_paths(g, edit_node(0, 0, b.size()));
        assert(-r.length[edit_node(a.size(), b.size(), b.size())] == edit_table_value(a, b));
        // Nodos y aristas: (n + 1)(m + 1) casillas, y 3nm + n + m lecturas.
        std::size_t arcs = 0;
        for (const auto& out : g) arcs += out.size();
        assert(g.size() == (a.size() + 1) * (b.size() + 1));
        assert(arcs == 3 * a.size() * b.size() + a.size() + b.size());
    }
    assert(edit_table_value("casa", "cosas") == 2);
    for (int round = 0; round < 500; round++) {
        std::vector<KnapsackObject> objects(rng() % 7);
        for (KnapsackObject& o : objects) o = {1 + rng() % 6, static_cast<long long>(rng() % 20)};
        const std::size_t capacity = rng() % 16;
        assert(knapsack_as_path(objects, capacity) == knapsack_brute_small(objects, capacity));
    }
    assert(knapsack_as_path({{2, 3}, {3, 4}, {4, 5}, {5, 8}}, 9) == 13);
}

void check_lis_answer(const std::vector<int>& a) {
    const std::vector<int> indices = lis_indices(a);
    for (std::size_t t = 0; t + 1 < indices.size(); t++) {
        assert(indices[t] < indices[t + 1]);
        assert(a[indices[t]] < a[indices[t + 1]]);
    }
    assert(indices.size() == lis_length_tails(a));
}

void test_lis() {
    std::mt19937 rng(93);
    for (int round = 0; round < 3000; round++) {
        std::vector<int> a(rng() % 13);
        const unsigned variety = 1 + rng() % 12;
        for (int& x : a) x = static_cast<int>(rng() % variety) - 3;
        const int best = lis_brute(a);
        assert(lis_length_quadratic(a) == best);
        assert(static_cast<int>(lis_length_tails(a)) == best);
        assert(static_cast<int>(non_decreasing_length_tails(a)) == lis_brute(a, false));
        check_lis_answer(a);

        // Es un camino más largo en el DAG con un origen virtual.
        const int n = static_cast<int>(a.size());
        WeightedDag g(a.size() + 1);
        for (int i = 0; i < n; i++) {
            g[n].push_back({i, 1});
            for (int j = i + 1; j < n; j++) {
                if (a[i] < a[j]) g[i].push_back({j, 1});
            }
        }
        const LongestPaths r = longest_paths(g, n);
        long long longest = 0;
        for (int i = 0; i < n; i++) longest = std::max(longest, r.length[i]);
        assert(longest == best);

        // La invariante del arreglo de finales, contra su definición.
        std::vector<int> tails;
        for (int i = 0; i < n; i++) {
            const auto it = std::lower_bound(tails.begin(), tails.end(), a[i]);
            if (it == tails.end()) {
                tails.push_back(a[i]);
            } else {
                *it = a[i];
            }
            assert(std::adjacent_find(tails.begin(), tails.end(), std::greater_equal<int>()) == tails.end());
            for (std::size_t k = 0; k < tails.size(); k++) {
                // Con el prefijo y solo valores menores o iguales que tails[k] hay largo k + 1,
                // y con valores estrictamente menores, no.
                std::vector<int> below;
                std::vector<int> strictly;
                for (int t = 0; t <= i; t++) {
                    if (a[t] <= tails[k]) below.push_back(a[t]);
                    if (a[t] < tails[k]) strictly.push_back(a[t]);
                }
                assert(lis_length_tails(below) >= k + 1);
                assert(lis_length_tails(strictly) < k + 1);
            }
        }
    }
    assert(lis_length_tails({}) == 0);
    assert(lis_indices({}).empty());
    assert(lis_length_tails({2, 2, 2}) == 1);
    assert(non_decreasing_length_tails({2, 2, 2}) == 3);
    assert(lis_length_tails({1, 3, 3, 5}) == 3);
    assert(non_decreasing_length_tails({1, 3, 3, 5}) == 4);
    assert(lis_greedy_take({5, 1, 2, 3}) == 1);
    assert(lis_length_tails({5, 1, 2, 3}) == 3);
    // Los finales no son una subsecuencia: el 1 está después del 3 y del 7.
    assert(final_tails({2, 5, 3, 7, 1}) == (std::vector<int>{1, 3, 7}));
    assert(lis_length_tails({1, 3, 7}) == 3 && lis_length_tails({2, 5, 3, 7, 1}) == 3);
}

void test_matrix_chain() {
    // La pregunta de repaso: 10x30, 30x5, 5x60.
    const std::vector<long long> review = {10, 30, 5, 60};
    const ChainTables t = matrix_chain(review);
    assert(t.cost[0][2] == 4500);
    assert(parenthesize(t.split, 0, 2) == "((A0 A1) A2)");
    assert(chain_brute(review) == 4500);

    std::mt19937 rng(94);
    for (int round = 0; round < 1000; round++) {
        std::vector<long long> d(2 + rng() % 7);
        for (long long& x : d) x = 1 + static_cast<long long>(rng() % 20);
        const long long last = static_cast<long long>(d.size()) - 2;
        const long long value = matrix_chain(d).cost[0][last];
        assert(value == chain_brute(d));
        assert(value == matrix_chain_i_descending(d));
        assert(chain_cheapest_first(d) >= value);
        assert(matrix_chain_naive_order(d).answer <= value);  // los ceros solo pueden bajarlo
        if (d.size() >= 4) assert(matrix_chain_naive_order(d).empty_reads > 0);
    }

    // Hay tantas parentizaciones como dice el número de Catalan: C(n - 1) para n matrices.
    std::vector<long long> catalan = {1};
    for (long long m = 0; m < 19; m++) catalan.push_back(catalan[m] * 2 * (2 * m + 1) / (m + 2));
    for (std::size_t n = 1; n <= 9; n++) {
        assert(static_cast<long long>(all_chain_plans(std::vector<long long>(n + 1, 2), 0, static_cast<int>(n) - 1).size()) == catalan[n - 1]);
    }
    assert(catalan[19] == 1767263190LL);

    // La cuenta de combinaciones: (n³ − n) / 6.
    for (long long n = 1; n <= 30; n++) {
        long long combinations = 0;
        matrix_chain(std::vector<long long>(static_cast<std::size_t>(n) + 1, 2), &combinations);
        assert(combinations == (n * n * n - n) / 6);
    }

    assert(chain_cheapest_first({4, 1, 3, 5}) == 72);
    assert(chain_brute({4, 1, 3, 5}) == 35);
    const std::vector<long long> d = {10, 20, 5, 30, 2};
    assert(matrix_chain_naive_order(d).answer < chain_brute(d));
    // Con una sola matriz no hay nada que multiplicar.
    assert(matrix_chain({3, 4}).cost[0][0] == 0);
    // Las dimensiones grandes piden long long: 2000 x 2000 x 2000 = 8e9.
    assert(matrix_chain({2000, 2000, 2000}).cost[0][1] == 8000000000LL);
}

void test_optimal_bst() {
    std::mt19937 rng(95);
    for (int round = 0; round < 1500; round++) {
        std::vector<long long> f(rng() % 8);
        for (long long& x : f) x = static_cast<long long>(rng() % 15);
        const int n = static_cast<int>(f.size());
        const BstTables t = optimal_bst(f);
        assert(t.cost[0][n] == bst_brute(f));
        std::vector<int> depth(f.size(), 0);
        bst_depths(t.root, 0, n, 1, depth);
        assert(cost_from_depths(f, depth) == t.cost[0][n]);
        assert(bst_most_frequent_root(f, 0, n, 1) >= t.cost[0][n]);
        assert(bst_balanced(f, 0, n, 1) >= t.cost[0][n]);
    }
    const std::vector<long long> example = {2, 1, 3, 4};
    assert(optimal_bst(example).cost[0][4] == 18);
    assert(bst_most_frequent_root(example, 0, 4, 1) == 20);
    assert(bst_balanced(example, 0, 4, 1) == 19);
    assert(optimal_bst({}).cost[0][0] == 0);
    assert(optimal_bst({7}).cost[0][1] == 7);
}

RootedTree random_tree(std::mt19937& rng, int n, int low, int high) {
    RootedTree tree{std::vector<std::vector<int>>(static_cast<std::size_t>(n)), 0, std::vector<long long>(static_cast<std::size_t>(n))};
    std::vector<int> label(static_cast<std::size_t>(n));
    std::iota(label.begin(), label.end(), 0);
    std::shuffle(label.begin(), label.end(), rng);
    tree.root = label[0];
    for (int i = 1; i < n; i++) {
        const int boss = label[rng() % static_cast<unsigned>(i)];
        tree.children[boss].push_back(label[i]);
    }
    for (long long& v : tree.value) v = low + static_cast<long long>(rng() % static_cast<unsigned>(high - low + 1));
    return tree;
}

bool is_independent(const RootedTree& tree, const std::vector<int>& nodes) {
    std::vector<char> inside(tree.value.size(), 0);
    for (int v : nodes) inside[v] = 1;
    for (std::size_t v = 0; v < tree.value.size(); v++) {
        if (!inside[v]) continue;
        for (int c : tree.children[v]) {
            if (inside[c]) return false;
        }
    }
    return true;
}

void test_tree_dp() {
    std::mt19937 rng(96);
    for (int round = 0; round < 4000; round++) {
        const int n = 1 + static_cast<int>(rng() % 13);
        const bool signed_values = round % 2 == 1;
        const RootedTree tree = random_tree(rng, n, signed_values ? -9 : 0, 20);
        const long long best = independent_set_brute(tree);
        assert(independent_set_value(tree) == best);
        assert(best >= 0);  // el conjunto vacío siempre es válido

        const std::vector<int> nodes = independent_set_nodes(tree);
        assert(is_independent(tree, nodes));
        long long total = 0;
        for (int v : nodes) total += tree.value[v];
        assert(total == best);

        // Los greedy y las versiones con error nunca se pasan si no eligen vecinos.
        assert(largest_value_first(tree) <= best);
        assert(forced_children_value(tree) <= best);
        if (!signed_values) assert(alternate_levels(tree) <= best);

        // Cobertura: directa, por fuerza bruta y por el complemento, también con pesos negativos.
        const long long sum = std::accumulate(tree.value.begin(), tree.value.end(), 0LL);
        const long long cover = min_cover_brute(tree);
        assert(min_cover_value(tree) == cover);

        // Los tamaños por orden inverso de BFS, contra un recorrido con pila desde cada nodo.
        const std::vector<int> sizes = subtree_sizes_bfs_order(tree.children, tree.root);
        for (int v = 0; v < n; v++) {
            int count = 0;
            std::vector<int> pending = {v};
            while (!pending.empty()) {
                const int u = pending.back();
                pending.pop_back();
                count++;
                for (int c : tree.children[u]) pending.push_back(c);
            }
            assert(sizes[v] == count);
        }
        assert(cover == sum - best);
    }

    // Los ejemplos del texto.
    const RootedTree chain{{{1}, {2}, {3}, {}}, 0, {10, 1, 1, 10}};
    assert(independent_set_value(chain) == 20);
    assert(forced_children_value(chain) == 11);
    assert(alternate_levels(chain) == 11);
    const RootedTree company{{{1, 2}, {3, 4}, {5}, {}, {}, {6, 7}, {}, {}}, 0, {4, 9, 6, 5, 5, 8, 3, 4}};
    assert(independent_set_value(company) > alternate_levels(company));
    assert(independent_set_value(company) > largest_value_first(company));
    assert(independent_set_value(company) > forced_children_value(company));
    assert(!is_independent(company, choose_each_node_alone(company)));
    const RootedTree negative{{{1, 2}, {}, {}}, 0, {-4, -1, -2}};
    assert(independent_set_value(negative) == 0);
    assert(independent_set_nodes(negative).empty());

    // Un árbol de un solo nodo, con valor positivo y negativo.
    assert(independent_set_value(RootedTree{{{}}, 0, {7}}) == 7);
    assert(independent_set_value(RootedTree{{{}}, 0, {-7}}) == 0);
    assert(min_cover_value(RootedTree{{{}}, 0, {7}}) == 0);
    assert(min_cover_value(RootedTree{{{}}, 0, {-7}}) == -7);  // una sola opción negativa sí conviene

    // Una cadena larga sin recursión: el orden inverso de BFS no usa la pila.
    const int n = 200000;
    std::vector<std::vector<int>> children(static_cast<std::size_t>(n));
    for (int v = 0; v + 1 < n; v++) children[v].push_back(v + 1);
    const std::vector<int> sizes = subtree_sizes_bfs_order(children, 0);
    assert(sizes[0] == n && sizes[n - 1] == 1);
}

}  // namespace

int main() {
    test_longest_path();
    test_longest_path_example();
    test_subproblem_paths();
    test_lis();
    test_matrix_chain();
    test_optimal_bst();
    test_tree_dp();
    std::cout << "Todas las pruebas de la semana 9 pasaron.\n";
}

Laboratorio

Archivos de partida del laboratorio de la semana.

lab/company_meeting_list/MakefileDescargar lab/company_meeting_list/Makefile
TIEMPO = 3
VERIFICADOR = verificador.cpp
include ../../../lab.mk
lab/company_meeting_list/README.mdDescargar lab/company_meeting_list/README.md
# La reunión de la empresa, con la lista

Esta es la extensión del problema `company_meeting`: la misma empresa, la misma regla y los mismos aportes. Ahora la
gerencia no se conforma con saber cuánto puede aportar la reunión. También quiere saber a quién invitar.

Nadie quiere asistir junto con su jefe directo. Una reunión sin nadie está permitida, y su aporte es 0.

## Entrada

La misma de `company_meeting`. La primera línea tiene n, la cantidad de personas, numeradas de 1 a n. Luego vienen n
líneas. La línea i tiene dos enteros: vᵢ, el aporte de la persona i, y bᵢ, el número de su jefe directo, o 0 si no
tiene jefe.

Los números no siguen el orden de la jerarquía. Hay exactamente una persona sin jefe, y las relaciones forman un árbol.

## Salida

Tres líneas:

1. El mayor aporte total posible.
2. k, la cantidad de personas invitadas.
3. Los k números de las personas invitadas, separados por espacios, en cualquier orden. Si k es 0, esta línea queda
   vacía.

Puede haber varias reuniones con el mayor aporte, y cualquiera vale: un verificador revisa su respuesta. Comprueba que
el total sea el mayor posible, que cada número esté entre 1 y n, que ninguno se repita, que nadie vaya junto con su
jefe directo y que los aportes de la lista sumen ese total.

## Límites

- 1 ≤ n ≤ 1 000 000
- −1 000 000 000 ≤ vᵢ ≤ 1 000 000 000
- 0 ≤ bᵢ ≤ n
- Hay un caso en que toda la empresa es una sola cadena de mando de 1 000 000 de personas.
- El programa corre con la pila del entorno del curso, de 8 MB (`ulimit -s` responde 8192).

## Ejemplo

Entrada:

```text
6
7 4
3 1
6 6
2 0
1 1
4 4
```

Salida:

```text
13
2
1 3
```

## Tiempo

3 segundos por caso, compilado con `-O2`. Corran `make test` para probar con el juez.
lab/company_meeting_list/solution.cppDescargar lab/company_meeting_list/solution.cpp
// La reunión de la empresa, con la lista: lee la entrada y propone una reunión vacía. Es una lista
// válida, pero casi nunca la mejor. Reemplacen el cálculo.
#include <cstddef>
#include <iostream>
#include <vector>

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::size_t n = 0;
    std::cin >> n;
    std::vector<int> value(n + 1, 0);
    std::vector<int> boss(n + 1, 0);
    for (std::size_t i = 1; i <= n; i++) std::cin >> value[i] >> boss[i];

    // Por hacer: calcular el mayor aporte y a quién invitar.
    std::cout << 0 << '\n' << 0 << '\n' << '\n';
}
lab/company_meeting_list/verificador.cppDescargar lab/company_meeting_list/verificador.cpp
// Verificador de "La reunión de la empresa, con la lista": verificador ENTRADA ESPERADA SALIDA.
// En ESPERADA está solo el mayor aporte. Aquí se comprueba que la salida diga ese total, y que su
// lista sea una reunión válida que suma eso. Termina con 0 si todo está bien, y con 1 y un mensaje si no.
#include <cstddef>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

namespace {

[[noreturn]] void reject(const std::string& reason) {
    std::cout << reason << '\n';
    std::exit(1);
}

// Convierte una palabra en número sin riesgo de overflow: signo opcional y a lo sumo 17 dígitos,
// que alcanzan para el mayor total posible, 10^15.
bool parse_number(const std::string& word, long long& value) {
    std::size_t start = word[0] == '-' ? 1 : 0;
    if (word.size() == start || word.size() - start > 17) return false;
    value = 0;
    for (std::size_t i = start; i < word.size(); i++) {
        if (word[i] < '0' || word[i] > '9') return false;
        value = value * 10 + (word[i] - '0');
    }
    if (start == 1) value = -value;
    return true;
}

}  // namespace

int main(int argc, char* argv[]) {
    if (argc != 4) reject("uso: verificador ENTRADA ESPERADA SALIDA");
    std::ifstream input(argv[1]);
    std::ifstream expected(argv[2]);
    std::ifstream output(argv[3]);

    long long n = 0;
    input >> n;
    std::vector<long long> value(static_cast<std::size_t>(n) + 1, 0);
    std::vector<long long> boss(static_cast<std::size_t>(n) + 1, 0);
    for (long long i = 1; i <= n; i++) input >> value[i] >> boss[i];

    long long best = 0;
    expected >> best;

    std::string word;
    long long claimed = 0;
    if (!(output >> word) || !parse_number(word, claimed)) reject("la primera palabra no es un número");
    if (claimed != best) reject("mayor aporte esperado: " + std::to_string(best) + ", salió " + word);

    long long count = 0;
    if (!(output >> word) || !parse_number(word, count)) reject("falta k, o no es un número");
    if (count < 0 || count > n) reject("k fuera de rango: " + word);

    std::vector<char> inside(static_cast<std::size_t>(n) + 1, 0);
    long long total = 0;
    for (long long t = 0; t < count; t++) {
        long long person = 0;
        if (!(output >> word)) reject("faltan personas en la lista");
        if (!parse_number(word, person)) reject("número inválido: " + word);
        if (person < 1 || person > n) reject("persona fuera de rango: " + word);
        if (inside[person]) reject("persona repetida: " + word);
        inside[person] = 1;
        total += value[person];
    }
    if (output >> word) reject("sobra texto después de la lista: " + word);

    for (long long i = 1; i <= n; i++) {
        if (inside[i] && inside[boss[i]]) {
            reject("la persona " + std::to_string(i) + " va junto con su jefe directo, " + std::to_string(boss[i]));
        }
    }
    if (total != best) reject("la lista suma " + std::to_string(total) + ", y debería sumar " + std::to_string(best));
    return 0;
}
lab/company_meeting/MakefileDescargar lab/company_meeting/Makefile
TIEMPO = 3
include ../../../lab.mk
lab/company_meeting/README.mdDescargar lab/company_meeting/README.md
# La reunión de la empresa

Una empresa quiere organizar una reunión con parte de su personal. Cada persona tiene un jefe directo, salvo la
directora o el director general, que no tiene jefe, y todas dependen de esa persona a través de su cadena de mando.

La gerencia estimó cuánto aporta cada persona a la reunión. El aporte puede ser negativo: alguien puede estorbar más
de lo que ayuda. Hay una sola regla: **nadie quiere asistir junto con su jefe directo**. Sí pueden asistir juntos una
persona y el jefe de su jefe, y también dos personas que tienen el mismo jefe.

Ustedes calculan el mayor aporte total que puede tener una reunión. Una reunión sin nadie está permitida, y su aporte
es 0.

## Entrada

La primera línea tiene n, la cantidad de personas, numeradas de 1 a n. Luego vienen n líneas. La línea i tiene dos
enteros: vᵢ, el aporte de la persona i, y bᵢ, el número de su jefe directo, o 0 si no tiene jefe.

Los números no siguen el orden de la jerarquía: el jefe de una persona puede tener un número mayor o menor que ella.
Hay exactamente una persona sin jefe, y las relaciones forman un árbol: desde cualquier persona, subiendo de jefe en
jefe, se llega a esa persona.

## Salida

Una línea con el mayor aporte total posible.

## Límites

- 1 ≤ n ≤ 1 000 000
- −1 000 000 000 ≤ vᵢ ≤ 1 000 000 000
- 0 ≤ bᵢ ≤ n
- Hay casos en que toda la empresa es una sola cadena de mando de 1 000 000 de personas.
- El programa corre con la pila del entorno del curso, de 8 MB (`ulimit -s` responde 8192).

## Ejemplo

Entrada:

```text
6
7 4
3 1
6 6
2 0
1 1
4 4
```

Salida:

```text
13
```

La persona 4 no tiene jefe. Sus subordinados directos son las personas 1 y 6, los de la persona 1 son las personas 2 y
5, y el de la persona 6 es la persona 3. La mejor reunión tiene a las personas 1 y 3, con aporte 7 + 6 = 13. Llevar a
las personas 4, 2, 5 y 3, que no tienen a su jefe directo en la sala, da 12.

## Tiempo

3 segundos por caso, compilado con `-O2`. Corran `make test` para probar con el juez.

Antes de ir por los casos grandes, conviene escribir una versión que pruebe todas las reuniones posibles, y comparar
con ella en empresas de pocas personas.
lab/company_meeting/solution.cppDescargar lab/company_meeting/solution.cpp
// La reunión de la empresa: lee la entrada y responde 0, como si nadie asistiera. Reemplacen el cálculo.
#include <cstddef>
#include <iostream>
#include <vector>

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::size_t n = 0;
    std::cin >> n;
    std::vector<int> value(n + 1, 0);
    std::vector<int> boss(n + 1, 0);
    for (std::size_t i = 1; i <= n; i++) std::cin >> value[i] >> boss[i];

    // Por hacer: calcular el mayor aporte total.
    std::cout << 0 << '\n';
}

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
# Semana 9: más programación dinámica. El DAG de subproblemas, camino más largo, subsecuencia
# creciente más larga, cadena de matrices, árboles de búsqueda óptimos y programación dinámica
# sobre árboles. Se usa dentro del entorno del curso.
#
#   make        compila los ejemplos en build/
#   make test   corre las pruebas de los ejemplos, con sanitizers
#   make bench  corre las mediciones, con -O2
#   make deep   muestra la recursión profunda que agota la pila, con -O2
#   make clean  borra lo generado
#
# Los problemas de lab/ tienen su propio Makefile y no entran aquí.

CXX = g++
CXXFLAGS = -Wall -Wextra -std=c++20 -g -fsanitize=address,undefined
BENCHFLAGS = -Wall -Wextra -std=c++20 -O2
BUILD = build
HEADERS = dag_dp.h lis.h interval_dp.h tree_dp.h
PROGRAMS = dag_demo lis_demo chain_demo bst_demo tree_demo test_examples
BENCHES = lis_bench chain_bench deep_chain

.PHONY: all test bench deep clean

all: $(PROGRAMS:%=$(BUILD)/%) $(BENCHES:%=$(BUILD)/%)

$(PROGRAMS:%=$(BUILD)/%): $(BUILD)/%: %.cpp $(HEADERS) | $(BUILD)
	$(CXX) $(CXXFLAGS) $< -o $@

# Lo que mide tiempos o la pila va con -O2 y sin sanitizers, como en el juez.
$(BENCHES:%=$(BUILD)/%): $(BUILD)/%: %.cpp $(HEADERS) | $(BUILD)
	$(CXX) $(BENCHFLAGS) $< -o $@

$(BUILD):
	mkdir -p $@

test: $(BUILD)/test_examples
	$(BUILD)/test_examples

bench: $(BUILD)/lis_bench $(BUILD)/chain_bench
	$(BUILD)/lis_bench
	$(BUILD)/chain_bench

deep: $(BUILD)/deep_chain
	-bash -c '$(BUILD)/deep_chain 100000; echo "código de salida: $$?"' 2>&1
	-bash -c '$(BUILD)/deep_chain 1000000; echo "código de salida: $$?"' 2>&1

clean:
	rm -rf $(BUILD)