Hotels
Las dos pistas siguientes se aplican a los tres enfoques:
Pista 1
Para cualesquiera tres nodos , y , los tres caminos , y tienen un único nodo en común.
Pista 2
Sea el nodo común mencionado. La terna es buena si y solo si las longitudes de los caminos , y son iguales.
Solución en video
Por David Zhou
Nota: la solución en video puede no ser la misma que las demás soluciones. Código en C++, Python y Java.
Solución en video
Solución 1 - Sumas de prefijos/sufijos
Pista 3
¿Crees que tienes una solución que no implica recorrer todas las ternas de nodos? Si es así, ¿es realmente o es en realidad amortizado?
Solución
Explicación
Como es pequeño, podemos calcular el número de ternas buenas con nodo común , para todo de 1 a .
Para algún , enraizamos el árbol en . Luego podemos hacer DFS para hallar las profundidades de los subárboles de los hijos de , y la cantidad de nodos a cada profundidad en cada subárbol. Sea \texttt{at\\_depth}_i[d] la cantidad de nodos a profundidad en el subárbol del hijo .
Cada nodo de una terna buena debe estar a la misma profundidad y en subárboles distintos, así que la cantidad de ternas buenas que aporta la profundidad del subárbol es:
\left(\sum_{j < i}\texttt{at\\_depth}_j[d]\right) \cdot \texttt{at\\_depth}_i[d] \cdot \left(\sum_{j > i}\texttt{at\\_depth}_j[d]\right)Podemos recorrer cada par válido en tiempo amortizado, ya que cada par corresponde a al menos un nodo del árbol.
¿Pero cómo calcular \sum_{j < i}\texttt{at\\_depth}_j[d] y \sum_{j > i}\texttt{at\\_depth}_j[d] rápidamente?
Nótese que solo tenemos que calcular una de estas sumas, y que se parecen mucho a sumas de prefijos/sufijos. En mi solución, calculo \sum_{j > i}\texttt{at\\_depth}_j[d] usando sumas de sufijos. Llamemos a este valor .
Como solo nos importan los de los pares válidos , podemos mantener un arreglo global que guarde los valores máximos de hasta el momento (con todos los elementos en 0 al inicio). Esto nos permite calcular todos los interesantes en tiempo .
Como alternativa, podemos ordenar los hijos de por sus profundidades y luego calcular de forma directa, lo cual sigue siendo lo bastante rápido para pasar.
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
vector<int> graph[5001];
void find_dep(int node, int parent, pair<int, vector<int>> &root, int depth = 0) {
if (depth == root.first) {
root.first++;
root.second.push_back(1);
} else root.second[depth]++;
for (int i : graph[node])
if (i != parent) find_dep(i, node, root, depth + 1);
}
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int a, b;
scanf("%d %d", &a, &b);
graph[a].push_back(b);
graph[b].push_back(a);
}
if (n < 4) {
printf("0\n");
return 0;
}
long long ans = 0;
for (int i = 1; i <= n; i++) {
int m = graph[i].size();
vector<pair<int, vector<int>>> dep(m);
vector<vector<int>> aft(m);
for (int j = 0; j < m; j++) find_dep(graph[i][j], i, dep[j]);
vector<int> tot(max_element(dep.begin(), dep.end())->first, 0);
for (int j = m - 1; ~j; j--) {
aft[j].resize(dep[j].first);
for (int k = 0; k < dep[j].first; k++) {
aft[j][k] = tot[k];
tot[k] += dep[j].second[k];
}
}
for (int j = 0; j < m; j++) {
for (int k = 0; k < dep[j].first; k++) {
ans += (tot[k] - dep[j].second[k] - aft[j][k]) * dep[j].second[k] *
aft[j][k];
}
}
}
printf("%lld\n", ans);
return 0;
}Hay que usar std::vectors para garantizar memoria .
Solución 2 - DP
Extra - Una solución
Ver el editorial polaco para más detalles.