Skip to Content

DP en árboles - Combinar subárboles

HechoFuenteNombreDificultadTagsSolución
CFKaren & SupermarketNormalen el módulo

Este fue el primer problema en el que vi este truco.

Solución

Para dos vectores aa y bb, definimos el vector c=abc=a\oplus b como el que tiene entradas ci=mink=0i(ak+bik)c_i=\min_{k=0}^i\left(a_k+b_{i-k}\right) para cada 0i<size(a)+size(b)10\le i < \text{size}(a)+\text{size}(b)-1.

De forma similar al editorial, definimos dp[x][0][g]\texttt{dp[x][0][g]} como el costo mínimo para comprar exactamente gg bienes del subárbol de xx si no usamos el cupón de xx, y definimos dp[x][1][g]\texttt{dp[x][1][g]} como el costo mínimo para comprar exactamente gg bienes del subárbol de xx si se nos permite usar el cupón de xx. Actualizamos dp[x][0]\texttt{dp[x][0]} con uno de los subárboles hijos tt de xx haciendo dp[x][0]=dp[x][0]dp[t][0]\texttt{dp[x][0]}=\texttt{dp[x][0]}\oplus \texttt{dp[t][0]}, y de forma análoga para dp[x][1]\texttt{dp[x][1]}.

#include <iostream> #include <vector> using namespace std; constexpr int MAX_GOODS = 5000; constexpr long long INF = 1e18; int initial[MAX_GOODS + 1]; int discounted[MAX_GOODS + 1]; vector<long long> dp[MAX_GOODS + 1][2]; vector<int> child[MAX_GOODS + 1]; vector<long long> total_min(vector<long long> a, vector<long long> b) { vector<long long> combined(a.size() + b.size() - 1, INF); for (int i = 0; i < a.size(); i++) { for (int j = 0; j < b.size(); j++) { combined[i + j] = min(combined[i + j], a[i] + b[j]); } } return combined; } void process(int g) { dp[g][0] = {0, initial[g]}; /* * we have INF for the first element because 0 would mess up merging * if it WAS 0, then that would mean we didn't buy this one * yet the algo would still think all the children are eligible for coupons */ dp[g][1] = {INF, discounted[g]}; for (int t : child[g]) { process(t); dp[g][0] = total_min(dp[g][0], dp[t][0]); dp[g][1] = total_min(dp[g][1], dp[t][1]); } for (int i = 0; i < dp[g][1].size(); i++) { dp[g][1][i] = min(dp[g][1][i], dp[g][0][i]); } } int main() { int good_num; int budget; cin >> good_num >> budget; for (int i = 1; i <= good_num; i++) { cin >> initial[i] >> discounted[i]; discounted[i] = initial[i] - discounted[i]; if (i > 1) { int prereq; cin >> prereq; child[prereq].push_back(i); } } process(1); for (int i = good_num; i >= 0; i--) { if (dp[1][1][i] <= budget) { cout << i << endl; break; } } }

El editorial calcula de forma naive una cota de O(N3)\mathcal{O}(N^3) para el tiempo de ejecución de esta solución. ¡Sin embargo, en realidad corre en O(N2)\mathcal{O}(N^2)!

Complejidad temporal de fusionar subárboles

La complejidad se puede demostrar con el siguiente problema:

Tenemos una lista de NN unos y un contador inicialmente en 00. Mientras la lista tenga más de un elemento, quitamos dos elementos cualesquiera aa y bb de la lista, sumamos aba\cdot b al contador y agregamos a+ba+b a la lista. En términos de NN, ¿cuál es el valor máximo posible del contador al final de este proceso?

Solución

El contador siempre será igual a (N2)\binom{N}{2} al final de este proceso; cada par de unos contribuye uno a la respuesta. Sumar aba\cdot b al contador corresponde a fusionar dos subárboles de tamaños aa y bb en un subárbol de tamaño a+ba+b en O(ab)\mathcal{O}(ab), lo que en total da una complejidad de O(N2)\mathcal{O}(N^2).

Problemas

HechoFuenteNombreDificultadTagsSolución
CEOI2017 - MuseumFácilSolución
COCI2019 - DzumbusNormalSolución
CFCereal Trees IINormalTree, DP
IOI2005 - RiversNormalSolución
ACTree PatrollingNormalDP, Tree
CFOstap & TreeNormalDPSolución
CFDiv 1 D - Miss PunyverseNormal
COCIPeriodniDifícilNTSolución