Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Tree Centroid Decomposition Algorithm Implementation

Tech Jul 28 1

Core Concepts

Tree Center Fundamentals

For any node in a tree, removing that node and its connected edges partitions the tree into several connected components. Let max_component[i] represent the size of the largest component after removing node i. The tree center is defined as the node that minimizes max_component[i].

A key property of tree centers is that the maximum component size of a tree center never exceeds half of the total nodes. This can be proven by contradiction.

To find the tree center, we compute size[x] (subtree size rooted at x) and for each node calculate max_component[x] = max(size[y] for y in children[x], total_nodes - size[x]). We include total_nodes - size[x] because nodes outside x's subtree form another component.


void find_subtree_size(int current, int parent) {
    int max_child = 0;
    subtree_size[current] = 1;
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        if (neighbor == parent) continue;
        
        find_subtree_size(neighbor, current);
        subtree_size[current] += subtree_size[neighbor];
        max_child = max(max_child, subtree_size[neighbor]);
    }
    
    max_child = max(max_child, total_nodes - subtree_size[current]);
    
    if (max_child < min_max_component) {
        centers.clear();
        min_max_component = max_child;
        centers.push_back(current);
    } else if (max_child == min_max_component) {
        centers.push_back(current);
    }
}

Algorithm Foundation

Centroid decomposition applies divide-and-conquer to solve tree path problems. In a rooted tree, paths can be categorized as either passing through the root or contained within subtrees. The algorithm first processes valid root-passing paths, then removes the root and recursively processes subtrees, converting non-root paths into root-passing paths within subtrees.

Selecting arbitrary roots could degrade complexity to O(n²) in worst-case scenarios (like chains). However, using the tree center as root ensures O(n log n) complexity. Since a center's maximum component size is at most n/2, each recursion level halves the problem size, resulting in logarithmic depth.

To find all valid root-passing paths, observe that such paths consist of left and right subtree chains. We process subtrees sequentially, using information from previously processed subtrees to compute current contributions.

Implementation Framework

The algorithm structure follows: find tree center, process subtree answers, recursive solve subtrees.


void locate_center(int current, int parent) {
    subtree_size[current] = 1;
    int max_component = 0;
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        int weight = edge->value;
        if (neighbor == parent || visited[neighbor]) continue;
        
        locate_center(neighbor, current);
        subtree_size[current] += subtree_size[neighbor];
        max_component = max(max_component, subtree_size[neighbor]);
    }
    
    max_component = max(max_component, current_tree_size - subtree_size[current]);
    
    if (max_component < min_component_size) {
        min_component_size = max_component;
        center_node = current;
    }
}

void compute_contributions(int current, int parent) {
    // Aggregate results from previously processed subtrees
}

void update_tracker(int current, int parent, int operation) {
    // Update tracking arrays after processing current subtree
}

void decompose_tree(int current) {
    visited[current] = true;
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        if (visited[neighbor]) continue;
        
        // Initialize values
        compute_contributions(neighbor, 0);
        update_tracker(neighbor, 0, 1);
    }
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        if (visited[neighbor]) continue;
        update_tracker(neighbor, 0, 0); // Reset tracking arrays
    }
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        if (visited[neighbor]) continue;
        current_tree_size = subtree_size[neighbor];
        center_node = 0;
        min_component_size = INT_MAX;
        locate_center(neighbor, 0);
        locate_center(center_node, 0); // Recompute sizes for accuracy
        decompose_tree(center_node);
    }
}

Practical Applications

Minimum Edge Path with Target Sum

Given a tree with weighted edges, find the path with exactly k total weight that uses the fewest edges.

We maintain a min_edges array where min_edges[d] stores the minimum edges needed to achieve distance d from the current root. During processing, we check if a path with length k - current_distance exists and update the answer accordingly.


void process_subtree(int current, int parent) {
    if (current_distance > target_sum) return;
    
    min_answer = min(min_answer, current_depth + min_edges[target_sum - current_distance]);
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        int weight = edge->value;
        if (neighbor == parent || visited[neighbor]) continue;
        
        current_distance += weight;
        current_depth += 1;
        process_subtree(neighbor, current);
    }
}

void update_distance_tracker(int current, int parent, int operation) {
    if (current_distance > target_sum) return;
    
    if (operation) {
        min_edges[current_distance] = min(min_edges[current_distance], current_depth);
    } else {
        min_edges[current_distance] = node_count;
    }
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        if (neighbor == parent || visited[neighbor]) continue;
        update_distance_tracker(neighbor, current, operation);
    }
}

Balanced Path Counting

Count paths where edge weights alternate between positive and negative values, and the path can be split at any point into two segments with equal sum.

We assign +1 to positive edges and -1 to negative edges. A path is balanced if its total sum is zero and can be split at some point where prefix sum equals suffix sum. We track distance occurrences with separate counters for paths with and without valid split points.


void traverse_subtree(int current, int parent) {
    max_depth = max(max_depth, abs(current_offset - base_offset));
    
    if (split_marker[current_offset]) {
        local_counter[current_offset][1]++;
    } else {
        local_counter[current_offset][0]++;
    }
    
    split_marker[current_offset]++;
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        int weight = edge->value;
        if (neighbor == parent || visited[neighbor]) continue;
        
        current_offset += weight;
        traverse_subtree(neighbor, current);
    }
    
    split_marker[current_offset]--;
}

Modular Path Counting

Count paths where the sum of edge weights is divisible by 3.

We maintain counters for distances modulo 3. The solution combines paths from different subtrees where the sum of their modular distances equals 0 mod 3.


void traverse_modular(int current, int parent) {
    modular_count[current_distance % 3]++;
    
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        int weight = edge->value;
        if (neighbor == parent || visited[neighbor]) continue;
        
        current_distance += weight;
        traverse_modular(neighbor, current);
    }
}

Maximum Product Path

Find the path that maximizes the product of minimum edge weight and total path weight.

We use a Fenwick tree to maintain maximum path weights for given minimum weights. For each path, we query the maximum path weight among all previously processed paths with minimum weight at least the current path's minimum.


void update_fenwick(int position, long long value) {
    while (position > 0) {
        fenwick[position] = max(fenwick[position], value);
        position -= position & -position;
    }
}

long long query_fenwick(int position) {
    long long result = 0;
    while (position <= max_value) {
        result = max(result, fenwick[position]);
        position += position & -position;
    }
    return result;
}

void process_paths(int current, int parent, long long current_min) {
    for (Edge* edge = adjacency_list[current]; edge != nullptr; edge = edge->next) {
        int neighbor = edge->target;
        int weight = edge->value;
        if (neighbor == parent || visited[neighbor]) continue;
        
        path_sum += weight;
        long long new_min = min(current_min, (long long)weight);
        
        max_answer = max(max_answer, new_min * (query_fenwick(new_min) + path_sum));
        process_paths(neighbor, current, new_min);
    }
}

Related Articles

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.