Advanced Tree Algorithms
Master tree diameter, centroid decomposition, and advanced tree DP techniques
Overview
Advanced tree algorithms are core techniques for solving complex tree problems. This chapter explores tree diameter algorithms, centroid decomposition, virtual tree construction, and other advanced techniques for handling large-scale tree queries and optimization problems.
Core Content
1. Tree Diameter Algorithm
// Tree diameter algorithm
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class TreeDiameter {
private:
int n;
vector<vector<pair<int, long long>>> adj;
// BFS to find farthest node
pair<int, long long> bfs(int start) {
vector<long long> dist(n, -1);
queue<int> q;
q.push(start);
dist[start] = 0;
int farthest = start;
long long maxDist = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto [v, weight] : adj[u]) {
if (dist[v] == -1) {
dist[v] = dist[u] + weight;
q.push(v);
if (dist[v] > maxDist) {
maxDist = dist[v];
farthest = v;
}
}
}
}
return {farthest, maxDist};
}
// DFS to calculate diameter
pair<long long, long long> dfs(int u, int parent) {
long long max1 = 0, max2 = 0; // Longest and second longest paths
for (auto [v, weight] : adj[u]) {
if (v != parent) {
auto [childMax, childDiameter] = dfs(v, u);
diameter = max(diameter, childDiameter);
long long pathLen = childMax + weight;
if (pathLen > max1) {
max2 = max1;
max1 = pathLen;
} else if (pathLen > max2) {
max2 = pathLen;
}
}
}
diameter = max(diameter, max1 + max2);
return {max1, diameter};
}
long long diameter;
public:
TreeDiameter(int vertices) : n(vertices), diameter(0) {
adj.resize(n);
}
void addEdge(int u, int v, long long weight = 1) {
adj[u].push_back({v, weight});
adj[v].push_back({u, weight});
}
// Method 1: Two BFS
long long getDiameterBFS() {
if (n == 0) return 0;
// First BFS to find one endpoint
auto [node1, dist1] = bfs(0);
// Second BFS to find other endpoint
auto [node2, diameter] = bfs(node1);
return diameter;
}
// Method 2: One DFS
long long getDiameterDFS() {
if (n == 0) return 0;
diameter = 0;
dfs(0, -1);
return diameter;
}
// Get diameter path
vector<int> getDiameterPath() {
if (n == 0) return {};
auto [node1, dist1] = bfs(0);
// Modified BFS to record path
vector<long long> dist(n, -1);
vector<int> parent(n, -1);
queue<int> q;
q.push(node1);
dist[node1] = 0;
int node2 = node1;
long long maxDist = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto [v, weight] : adj[u]) {
if (dist[v] == -1) {
dist[v] = dist[u] + weight;
parent[v] = u;
q.push(v);
if (dist[v] > maxDist) {
maxDist = dist[v];
node2 = v;
}
}
}
}
// Reconstruct path
vector<int> path;
int current = node2;
while (current != -1) {
path.push_back(current);
current = parent[current];
}
return path;
}
};
// Centroid decomposition
class CentroidDecomposition {
private:
int n;
vector<vector<int>> adj;
vector<bool> removed;
vector<int> subtreeSize;
// Calculate subtree size
int calculateSize(int u, int parent) {
subtreeSize[u] = 1;
for (int v : adj[u]) {
if (v != parent && !removed[v]) {
subtreeSize[u] += calculateSize(v, u);
}
}
return subtreeSize[u];
}
// Find centroid
int findCentroid(int u, int parent, int treeSize) {
for (int v : adj[u]) {
if (v != parent && !removed[v] && subtreeSize[v] > treeSize / 2) {
return findCentroid(v, u, treeSize);
}
}
return u;
}
// Decomposition process
void decompose(int u, int parent) {
int treeSize = calculateSize(u, -1);
int centroid = findCentroid(u, -1, treeSize);
removed[centroid] = true;
// Process centroid
processCentroid(centroid);
// Recursively decompose subtrees
for (int v : adj[centroid]) {
if (!removed[v]) {
decompose(v, centroid);
}
}
}
// Process centroid logic (customizable)
void processCentroid(int centroid) {
cout << "Processing centroid: " << centroid << endl;
// Add specific processing logic here
// Example: calculate paths through centroid
}
public:
CentroidDecomposition(int vertices) : n(vertices) {
adj.resize(n);
removed.resize(n);
subtreeSize.resize(n);
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void decompose() {
fill(removed.begin(), removed.end(), false);
decompose(0, -1);
}
};
// Virtual tree construction
class VirtualTree {
private:
int n, timer;
vector<vector<int>> adj;
vector<int> depth, parent, tin, tout;
vector<vector<int>> up; // Binary lifting
void dfs(int u, int p, int d) {
tin[u] = timer++;
depth[u] = d;
parent[u] = p;
up[u][0] = p;
for (int i = 1; i < up[u].size(); i++) {
if (up[u][i-1] != -1) {
up[u][i] = up[up[u][i-1]][i-1];
}
}
for (int v : adj[u]) {
if (v != p) {
dfs(v, u, d + 1);
}
}
tout[u] = timer++;
}
bool isAncestor(int u, int v) {
return tin[u] <= tin[v] && tout[u] >= tout[v];
}
int lca(int u, int v) {
if (isAncestor(u, v)) return u;
if (isAncestor(v, u)) return v;
for (int i = up[u].size() - 1; i >= 0; i--) {
if (up[u][i] != -1 && !isAncestor(up[u][i], v)) {
u = up[u][i];
}
}
return up[u][0];
}
public:
VirtualTree(int vertices) : n(vertices), timer(0) {
adj.resize(n);
depth.resize(n);
parent.resize(n);
tin.resize(n);
tout.resize(n);
int logN = 0;
while ((1 << logN) <= n) logN++;
up.assign(n, vector<int>(logN, -1));
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void preprocess(int root = 0) {
timer = 0;
dfs(root, -1, 0);
}
// Build virtual tree
vector<vector<int>> buildVirtualTree(vector<int>& important) {
if (important.empty()) return {};
// Sort by DFS order
sort(important.begin(), important.end(), [&](int a, int b) {
return tin[a] < tin[b];
});
// Add LCA nodes
vector<int> nodes = important;
for (int i = 0; i < important.size() - 1; i++) {
int l = lca(important[i], important[i + 1]);
nodes.push_back(l);
}
// Remove duplicates and sort
sort(nodes.begin(), nodes.end());
nodes.erase(unique(nodes.begin(), nodes.end()), nodes.end());
sort(nodes.begin(), nodes.end(), [&](int a, int b) {
return tin[a] < tin[b];
});
// Build virtual tree
vector<vector<int>> vtree(n);
vector<int> stack;
for (int u : nodes) {
while (!stack.empty() && !isAncestor(stack.back(), u)) {
stack.pop_back();
}
if (!stack.empty()) {
vtree[stack.back()].push_back(u);
}
stack.push_back(u);
}
return vtree;
}
};
int main() {
// Test tree diameter
TreeDiameter td(6);
td.addEdge(0, 1, 2);
td.addEdge(1, 2, 3);
td.addEdge(1, 3, 1);
td.addEdge(3, 4, 4);
td.addEdge(3, 5, 2);
cout << "BFS method diameter: " << td.getDiameterBFS() << endl;
cout << "DFS method diameter: " << td.getDiameterDFS() << endl;
vector<int> path = td.getDiameterPath();
cout << "Diameter path: ";
for (int i = 0; i < path.size(); i++) {
cout << path[i];
if (i < path.size() - 1) cout << " -> ";
}
cout << endl;
// Test centroid decomposition
CentroidDecomposition cd(7);
cd.addEdge(0, 1);
cd.addEdge(1, 2);
cd.addEdge(1, 3);
cd.addEdge(3, 4);
cd.addEdge(3, 5);
cd.addEdge(5, 6);
cout << "Centroid decomposition:" << endl;
cd.decompose();
// Test virtual tree
VirtualTree vt(8);
vt.addEdge(0, 1);
vt.addEdge(0, 2);
vt.addEdge(1, 3);
vt.addEdge(1, 4);
vt.addEdge(2, 5);
vt.addEdge(2, 6);
vt.addEdge(6, 7);
vt.preprocess(0);
vector<int> important = {3, 4, 7};
vector<vector<int>> vtree = vt.buildVirtualTree(important);
cout << "Virtual tree construction completed" << endl;
return 0;
}Problem-Solving Tips
🎯 Algorithm Selection
Use two BFS or one DFS for tree diameter, centroid decomposition for path problems, virtual tree for specific node queries.
⚡ Complexity Analysis
Diameter algorithm O(n), centroid decomposition O(n log n), virtual tree construction O(k log k) where k is number of important nodes.
🔧 Implementation Points
Centroid decomposition requires proper maintenance of removed array, virtual tree needs LCA preprocessing and DFS order sorting.
🧮 Application Scenarios
Efficient solutions for advanced tree problems like distance queries, path counting, and subtree operation optimization.