City Attractions
Introducción
Sea el nodo al que va Gigel desde el nodo . Como el grafo formado por las aristas dirigidas es un grafo funcional, podemos usar elevación binaria (binary jumping) o cualquier otro método eficiente para hallar el nodo final.
¡Ahora solo necesitamos hallar todos los y listo! Sin embargo, esto no es tan directo como suena…
Un problema más simple
Consideremos un problema más simple: supongamos que enraizamos el árbol en el nodo y Gigel solo puede moverse hacia abajo en el árbol (sin preocuparnos por las hojas). En este problema, podemos hallar todos los (y ) usando una simple DP en árboles:
Sea el nodo en el subárbol de (excluyendo mismo) tal que se maximiza. Además, guardamos este valor en el arreglo de DP. Podemos hallar tomando el mejor entre y sobre todos los hijos de .
Este algoritmo corre en tiempo .
Hallar todos los
Obviamente, la solución del problema más simple no resuelve el problema general: ¡podríamos necesitar subir al padre de un nodo!
Para resolver esto, podemos primero hacer un DFS para hallar como se definió arriba, y luego un segundo DFS para permitir movernos fuera de nuestro subárbol. Ver el módulo de resolver para todas las raíces si no se está familiarizado con esta técnica. Esencialmente, hallamos el mejor destino desde si subimos al padre de y luego lo comparamos con .
Después de hacer esto, es simplemente como queríamos.
Hallar el destino final
Hay dos formas de hallar la ubicación final de Gigel.
- Podemos implementar elevación binaria (binary jumping) sobre nuestro arreglo que contiene la siguiente ubicación
- Intentamos llegar a un ciclo, y luego tomamos nuestros saltos restantes módulo el tamaño del ciclo
El primer método es un poco más fácil de implementar, pero introduce un factor logarítmico.
Implementación 1
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int n;
ll k;
cin >> n >> k;
vector<int> a(n);
for (int &i : a) { cin >> i; }
vector<vector<int>> adj(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--, v--;
adj[u].push_back(v);
adj[v].push_back(u);
}
const array<int, 2> def = {-(n + 1), -1};
vector<array<int, 2>> top(n, def);
vector<array<int, 2>> sub(n, def);
/** @return best next node in subtree of u */
const auto get = [&](int u) -> array<int, 2> {
return max(array<int, 2>{sub[u][0] - 1, sub[u][1]},
array<int, 2>{a[u] - 1, -u});
};
// calculamos el mejor vértice al que ir en el subárbol actual
function<void(int, int)> dfs = [&](int u, int p) {
for (const int v : adj[u]) {
if (v == p) { continue; }
dfs(v, u);
sub[u] = max(sub[u], get(v));
}
};
dfs(0, -1);
vector<int> next_node(n);
// calculamos el mejor vértice al que ir fuera del subárbol actual
function<void(int, int)> reroot = [&](int u, int p) {
next_node[u] = -max(top[u], sub[u])[1];
array<int, 2> best = top[u];
array<int, 2> alt = def;
for (const int v : adj[u]) {
if (v == p) { continue; }
const array<int, 2> cur = get(v);
if (cur > best) {
alt = best, best = cur;
} else if (cur > alt) {
alt = cur;
}
}
for (const int v : adj[u]) {
if (v == p) { continue; }
top[v] = get(v) == best ? alt : best;
top[v][0]--;
top[v] = max(top[v], array<int, 2>{a[u] - 1, -u});
reroot(v, u);
}
};
reroot(0, -1);
int res = 0;
for (int i = 0; i < 63; i++) {
if ((k >> i) & 1) { res = next_node[res]; }
vector<int> new_next(n);
for (int j = 0; j < n; j++) { new_next[j] = next_node[next_node[j]]; }
next_node = move(new_next);
}
cout << res + 1 << endl;
}Implementación 2
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int n;
ll k;
cin >> n >> k;
vector<int> a(n);
for (int &i : a) { cin >> i; }
vector<vector<int>> adj(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--, v--;
adj[u].push_back(v);
adj[v].push_back(u);
}
const array<int, 2> def = {-(n + 1), -1};
vector<array<int, 2>> top(n, def);
vector<array<int, 2>> sub(n, def);
/** @return best next node in subtree of u */
const auto get = [&](int u) -> array<int, 2> {
return max(array<int, 2>{sub[u][0] - 1, sub[u][1]},
array<int, 2>{a[u] - 1, -u});
};
// calculamos el mejor vértice al que ir en el subárbol actual
function<void(int, int)> dfs = [&](int u, int p) {
for (const int v : adj[u]) {
if (v == p) { continue; }
dfs(v, u);
sub[u] = max(sub[u], get(v));
}
};
dfs(0, -1);
vector<int> next_node(n);
// calculamos el mejor vértice al que ir fuera del subárbol actual
function<void(int, int)> reroot = [&](int u, int p) {
next_node[u] = -max(top[u], sub[u])[1];
array<int, 2> best = top[u];
array<int, 2> alt = def;
for (const int v : adj[u]) {
if (v == p) { continue; }
const array<int, 2> cur = get(v);
if (cur > best) {
alt = best, best = cur;
} else if (cur > alt) {
alt = cur;
}
}
for (const int v : adj[u]) {
if (v == p) { continue; }
top[v] = get(v) == best ? alt : best;
top[v][0]--;
top[v] = max(top[v], array<int, 2>{a[u] - 1, -u});
reroot(v, u);
}
};
reroot(0, -1);
int res = 0;
if (k <= n) {
for (int i = 0; i < k; i++) { res = next_node[res]; }
} else {
k -= n;
for (int i = 0; i < n; i++) { res = next_node[res]; }
vector<bool> vis(n);
vector<int> path;
while (!vis[res]) {
vis[res] = true;
path.push_back(res);
res = next_node[res];
}
res = path[k % path.size()];
}
cout << res + 1 << endl;
}