Skip to Content

Fixed-Length Paths I

Complejidad temporal: O(NlogN)\mathcal O(N \log N)

Dado un nodo uu, sabemos que cualquier camino en el árbol o bien pasa por uu o bien está completamente contenido en uno de los subárboles de los hijos de uu. Esto significa que para resolver el problema en un árbol que contiene uu, podemos:

  1. Contar el número de caminos de longitud kk que pasan por uu.
  2. Quitar uu y sus aristas incidentes del árbol.
  3. Resolver el problema de forma recursiva en los nuevos árboles que se forman.

Contar caminos que pasan por uu

Podemos contar el número de caminos que pasan por uu en un árbol de tamaño MM en tiempo O(M)\mathcal O(M).

Procesamos cada uno de los subárboles de los hijos de uu en orden. Sea cnti[d]\texttt{cnt}_i[d] el número de nodos que hemos visitado con profundidad dd después de procesar los primeros ii subárboles de los hijos de uu. Un nodo con profundidad dd en el ii-ésimo subárbol de un hijo de uu contribuirá cnti1[kd]\texttt{cnt}_{i - 1}[k - d] caminos a la respuesta.

Usando dos DFS para cada hijo, podemos calcular su contribución y luego actualizar cnt\texttt{cnt} en tiempo total O(M)\mathcal O(M).

Aplicar descomposición por centroide

Podemos combinar la idea anterior con descomposición por centroide para resolver todo el problema en tiempo O(NlogN)\mathcal O(N \log N).

Al procesar algún árbol, simplemente hacemos que el uu que usamos sea el centroide de ese árbol. Como el árbol de centroides tiene profundidad O(logN)\mathcal O(\log N) y cada capa del árbol de centroides toma tiempo amortizado O(N)\mathcal O(N) para procesarse, la complejidad total es O(NlogN)\mathcal O(N \log N).

Implementación

#include <bits/stdc++.h> typedef long long ll; using namespace std; int n, k; vector<int> graph[200001]; int subtree[200001]; ll ans = 0; int cnt[200001]{1}, mx_depth; bool processed[200001]; int get_subtree_sizes(int node, int parent = 0) { subtree[node] = 1; for (int i : graph[node]) if (!processed[i] && i != parent) subtree[node] += get_subtree_sizes(i, node); return subtree[node]; } int get_centroid(int desired, int node, int parent = 0) { for (int i : graph[node]) if (!processed[i] && i != parent && subtree[i] >= desired) return get_centroid(desired, i, node); return node; } void get_cnt(int node, int parent, bool filling, int depth = 1) { if (depth > k) return; mx_depth = max(mx_depth, depth); if (filling) cnt[depth]++; else ans += cnt[k - depth]; for (int i : graph[node]) if (!processed[i] && i != parent) get_cnt(i, node, filling, depth + 1); } void centroid_decomp(int node = 1) { int centroid = get_centroid(get_subtree_sizes(node) >> 1, node); processed[centroid] = true; mx_depth = 0; for (int i : graph[centroid]) if (!processed[i]) { get_cnt(i, centroid, false); get_cnt(i, centroid, true); } fill(cnt + 1, cnt + mx_depth + 1, 0); for (int i : graph[centroid]) if (!processed[i]) centroid_decomp(i); } int main() { cin.tie(0)->sync_with_stdio(0); cin >> n >> k; for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; graph[u].push_back(v); graph[v].push_back(u); } centroid_decomp(); cout << ans; return 0; }