Open In App

DP on Trees for Competitive Programming

Dynamic Programming (DP) on trees is a powerful algorithmic technique commonly used in competitive programming. It involves solving various tree-related problems by efficiently calculating and storing intermediate results to optimize time complexity. By using the tree structure, DP on trees allows programmers to find solutions for a wide range of problems, making it an essential skill in competitive programming.

DP on Trees for Competitive Programming

Dynamic Programming (DP) on Tree:

Dynamic Programming (DP) on trees is a technique used to efficiently solve problems involving tree structures by breaking them down into smaller subproblems and storing intermediate results to avoid redundant calculations.



How does Dynamic Programming (DP) on Tree works?

Lets, understand the Dynamic Programming (DP) on by a problem:

Problem: Given a tree consisting of N Nodes and N-1 edges. The task is to select the maximum set of edges such that each vertex is part of at most one of the selected edges (no two edges share a common end point) i.e., if we select an edge connecting vertex u and v, then we cannot select any other edge connected by vertex u or vertex v.



Approach: The problem can be solved using DP on Tree in the following way:

Root the tree: Root the tree at any of node(say 1).

Define the states: For each node we have 2 options, select a edge from the current from the current vertex to any of his child or do not select any edge passing through the current vertex.

dp[v][0]= Maximum set of edges selected in the subtree rooted at vertex v such that no two edges have a common end point, if we do not select any edge passing through vertex v.

dp[v][1]= Maximum set of edges selected in the subtree rooted at vertex v such that no two edges have a common end point, if we select a edge passing through vertex v.

Make the Transition:

Calculating dp[v][0]: Since dp[v][0] defines the state where we do not take any edge passing through vertex v (i.,e, v->u , where u is child of vertex). So for the all child nodes of vertex v we can either take an edge passing through it or do not take, and all the children of vertex v will be independent of each other in this case.

, where u is child of vertex v.

Calculating dp[v][1]: dp[v][1] defines the state where we do take a edge passing through vertex v (i.,e, v->u , where u is child of vertex).

Suppose we select a edge (v->u), then we cannot choose any other edge passing through vertex u. So will need dp[u][0] in this case, and for all other children of vertex v, we can either choose an edge passing through it or do not choose it as done in above case and we can use that state (dp[v][0]) for calculating this state.

, for all child u of vertex v.

Since we have chosen vertex as the 1 root. Our answer will be max(dp[1][0], dp[1][1]).

Below is the implementation of above approach:

#include <bits/stdc++.h>
using namespace std;
 
// Function to perform dynamic programming on the tree.
void dp_on_tree(vector<vector<int> >& adj, int curr,
                int prev, vector<vector<int> >& dp,
                int& ans)
{
    for (auto x : adj[curr]) {
        if (x != prev) {
 
            // Recursively calculate the dp values for each
            // node.
            dp_on_tree(adj, x, curr, dp, ans);
 
            // Update dp[curr][0] by taking the maximum of
            // not selecting and selecting edges.
            dp[curr][0] += max(dp[x][0], dp[x][1]);
        }
    }
 
    for (auto x : adj[curr]) {
        if (x != prev) {
            // Calculate dp[curr][1] using dp values of
            // children.
            dp[curr][1]
                = max(dp[curr][1],
                      (1 + dp[x][0])
                          + (dp[curr][0]
                             - max(dp[x][0], dp[x][1])));
        }
    }
 
    // Update the global maximum answer with the maximum of
    // dp values for the current node.
    ans = max(ans, max(dp[curr][0], dp[curr][1]));
}
 
// Function to solve the problem and find the maximum set of
// edges.
void solve(int n, vector<vector<int> >& edges)
{
    vector<vector<int> > adj(n + 1);
    // Create an adjacency list to represent the tree.
    for (int i = 0; i < n - 1; i++) {
        int x = edges[i][0];
        int y = edges[i][1];
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
 
    // Initialize the answer.
    int ans = 0;
 
    // Initialize the dp array.
    vector<vector<int> > dp(n + 1, vector<int>(2, 0));
 
    // Start dynamic programming on the tree with the root
    // node as 1.
    dp_on_tree(adj, 1, -1, dp, ans);
 
    // Output the maximum set of edges.
    cout << ans << "\n";
}
 
// Driver Code
int main()
{
    int N = 5;
    vector<vector<int> > edges
        = { { 1, 2 }, { 1, 3 }, { 3, 4 }, { 3, 5 } };
    solve(N, edges);
}

                    
import java.util.ArrayList;
 
public class MaximumSetOfEdges {
 
    // Function to perform dynamic programming on the tree
    static void dpOnTree(ArrayList<ArrayList<Integer>> adj, int curr, int prev, ArrayList<ArrayList<Integer>> dp, int[] ans) {
        // Traverse through adjacent nodes
        for (int x : adj.get(curr)) {
            if (x != prev) {
                // Recursively calculate DP values for each node
                dpOnTree(adj, x, curr, dp, ans);
                // Update DP[curr][0] by taking the maximum of not selecting and selecting edges
                dp.get(curr).set(0, dp.get(curr).get(0) + Math.max(dp.get(x).get(0), dp.get(x).get(1)));
            }
        }
 
        // Traverse through adjacent nodes again
        for (int x : adj.get(curr)) {
            if (x != prev) {
                // Calculate DP[curr][1] using DP values of children
                dp.get(curr).set(1, Math.max(dp.get(curr).get(1), (1 + dp.get(x).get(0)) + (dp.get(curr).get(0) - Math.max(dp.get(x).get(0), dp.get(x).get(1)))));
            }
        }
 
        // Update the global maximum answer with the maximum of DP values for the current node
        ans[0] = Math.max(ans[0], Math.max(dp.get(curr).get(0), dp.get(curr).get(1)));
    }
 
    // Function to solve the problem and find the maximum set of edges
    static void solve(int n, int[][] edges) {
        ArrayList<ArrayList<Integer>> adj = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            adj.add(new ArrayList<>());
        }
 
        // Create an adjacency list to represent the tree
        for (int i = 0; i < n - 1; i++) {
            int x = edges[i][0];
            int y = edges[i][1];
            adj.get(x).add(y);
            adj.get(y).add(x);
        }
 
        int[] ans = {0};
        ArrayList<ArrayList<Integer>> dp = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            dp.add(new ArrayList<>(2));
            dp.get(i).add(0);
            dp.get(i).add(0);
        }
 
        // Start dynamic programming on the tree with the root node as 1
        dpOnTree(adj, 1, -1, dp, ans);
        System.out.println(ans[0]);
    }
 
    public static void main(String[] args) {
        int N = 5;
        int[][] edges = {{1, 2}, {1, 3}, {3, 4}, {3, 5}};
        solve(N, edges);
    }
}

                    
# Function to perform dynamic programming on the tree.
def dp_on_tree(adj, curr, prev, dp, ans):
    for x in adj[curr]:
        if x != prev:
            # Recursively calculate the dp values for each node.
            dp_on_tree(adj, x, curr, dp, ans)
 
            # Update dp[curr][0] by taking the maximum of not selecting and selecting edges.
            dp[curr][0] += max(dp[x][0], dp[x][1])
 
    for x in adj[curr]:
        if x != prev:
            # Calculate dp[curr][1] using dp values of children.
            dp[curr][1] = max(dp[curr][1], (1 + dp[x][0]) +
                              (dp[curr][0] - max(dp[x][0], dp[x][1])))
 
    # Update the global maximum answer with the maximum of dp values for the current node.
    ans[0] = max(ans[0], max(dp[curr][0], dp[curr][1]))
 
# Function to solve the problem and find the maximum set of edges.
 
 
def solve(n, edges):
    adj = [[] for _ in range(n + 1)]
 
    # Create an adjacency list to represent the tree.
    for edge in edges:
        x, y = edge
        adj[x].append(y)
        adj[y].append(x)
 
    # Initialize the answer.
    ans = [0]
 
    # Initialize the dp array.
    dp = [[0, 0] for _ in range(n + 1)]
 
    # Start dynamic programming on the tree with the root node as 1.
    dp_on_tree(adj, 1, -1, dp, ans)
 
    # Output the maximum set of edges.
    print(ans[0])
 
 
# Driver Code
if __name__ == "__main__":
    N = 5
    edges = [[1, 2], [1, 3], [3, 4], [3, 5]]
    solve(N, edges)

                    
using System;
using System.Collections.Generic;
using System.Linq;
 
class MaximumSetOfEdges
{
    // Function to perform dynamic programming on the tree.
    static int dp_on_tree(List<List<int>> adj, int curr, int prev, List<List<int>> dp, ref int ans)
    {
        // Traverse the adjacent nodes
        foreach (int x in adj[curr])
        {
            // If not the previous node, perform DP recursively
            if (x != prev)
            {
                dp[curr][0] += Math.Max(dp_on_tree(adj, x, curr, dp, ref ans), dp[x][1]);
            }
        }
 
        foreach (int x in adj[curr])
        {
            if (x != prev)
            {
                dp[curr][1] = Math.Max(dp[curr][1],
                                       (1 + dp[x][0]) + (dp[curr][0] - Math.Max(dp[x][0],
                                                                                dp[x][1])));
            }
        }
 
        // Update the global maximum answer
        ans = Math.Max(ans, Math.Max(dp[curr][0], dp[curr][1]));
        return dp[curr][0];
    }
 
    // Function to solve the problem and find the maximum set of edges.
    static void Solve(int n, List<List<int>> edges)
    {
        // Create adjacency list
        List<List<int>> adj = new List<List<int>>();
        for (int i = 0; i <= n; i++)
        {
            adj.Add(new List<int>());
        }
 
        // Construct the adjacency list for the tree
        foreach (List<int> edge in edges)
        {
            int x = edge[0];
            int y = edge[1];
            adj[x].Add(y);
            adj[y].Add(x);
        }
 
        int ans = 0;
        // Initialize DP array
        List<List<int>> dp = new List<List<int>>();
        for (int i = 0; i <= n; i++)
        {
            dp.Add(new List<int> { 0, 0 });
        }
 
        // Perform DP on the tree with root node as 1
        dp_on_tree(adj, 1, -1, dp, ref ans);
        Console.WriteLine(ans); // Output the maximum set of edges
    }
 
    static void Main()
    {
        int N = 5;
        // Define edges of the tree
        List<List<int>> edges = new List<List<int>>
        {
            new List<int> { 1, 2 },
            new List<int> { 1, 3 },
            new List<int> { 3, 4 },
            new List<int> { 3, 5 }
        };
        Solve(N, edges);
    }
}

                    
// Function to perform dynamic programming on the tree.
function dp_on_tree(adj, curr, prev, dp, ans) {
    for (const x of adj[curr]) {
        if (x !== prev) {
 
            // Recursively calculate the dp values for each node.
            dp_on_tree(adj, x, curr, dp, ans);
 
            // Update dp[curr][0] by taking the maximum of
            // not selecting and selecting edges.
            dp[curr][0] += Math.max(dp[x][0], dp[x][1]);
        }
    }
 
    for (const x of adj[curr]) {
        if (x !== prev) {
            // Calculate dp[curr][1] using dp values of children.
            dp[curr][1] = Math.max(dp[curr][1],
                (1 + dp[x][0]) + (dp[curr][0] - Math.max(dp[x][0], dp[x][1])));
        }
    }
 
    // Update the global maximum answer with the maximum of
    // dp values for the current node.
    ans[0] = Math.max(ans[0], Math.max(dp[curr][0], dp[curr][1]));
}
 
// Function to solve the problem and find the maximum set of edges.
function solve(n, edges) {
    const adj = new Array(n + 1).fill(0).map(() => []);
 
    // Create an adjacency list to represent the tree.
    for (const edge of edges) {
        const x = edge[0];
        const y = edge[1];
        adj[x].push(y);
        adj[y].push(x);
    }
 
    // Initialize the answer.
    const ans = [0];
 
    // Initialize the dp array.
    const dp = new Array(n + 1).fill(0).map(() => new Array(2).fill(0));
 
    // Start dynamic programming on the tree with the root node as 1.
    dp_on_tree(adj, 1, -1, dp, ans);
 
    // Output the maximum set of edges.
    console.log(ans[0]);
}
 
// Driver Code
const N = 5;
const edges = [[1, 2], [1, 3], [3, 4], [3, 5]];
solve(N, edges);

                    

Output
2













Time Complexity: O(N)
Auxilary Space: O(N)

Rerooting Technique:

The Rerooting technique is a method used in tree algorithms to efficiently compute values associated with nodes or subtrees of a rooted tree when the tree’s root can be moved or “rerooted” to different nodes in the tree. This technique is particularly useful when you want to calculate values for various nodes or subtrees in a tree, but you don’t want to traverse the entire tree for each query.

How does Rerooting Technique works?

The rerooting technique is commonly used in problems that require dynamic programming on trees, such as finding optimal paths, maximum values, or minimum values in subtrees. It allows you to reposition the root and update the values in a way that minimizes redundant work, resulting in more efficient algorithms for various tree-related problems.

Lets, understand the Rerooting Techique by a problem:

Problem: Given a tree consisting of N Nodes and N-1 edges. The task is to determine for each node the sum of distance to all other nodes.
The problem can be solved using Rerooting technique in the following way:

Approach:

Root the tree: Root the tree at node 1.

Define the states: We will need 2 dp arrays for this problem.

dp1[v]: The sum of distance of node v from all the other nodes in subtree rooted at vertex v.

dp2[v]: The sum of distance of node v from all the other other nodes in the tree.

Calculate the answer for the root: For the root, dp1[1] will be equal to dp2[1] since, all the nodes of the tree lie in the subtree rooted at vertex 1.

dp1[v] can be calculated by:-

, where u is the child of vertex v and sub[u] is the number of nodes in subtree rooted at vertex u.

Since dp2[1]=dp1[1], answer for the root have been determined.

Calculate the answer for other nodes (Rerooting Step): Now suppose we know the final answer for vertex v, dp2[v] (sum of distance of node v from all other nodes). Now we want to compute the answer for node u, which is a child of vertex v.

dp2[u], (sum of distance from node u to all other nodes in tree) will be the sum of following two parts:

  1. Sum of distance from node u to all other nodes in subtree rooted at vertex u.
    We already computed dp1[u], which is sum of distance from node u to all other nodes in the subtree rooted at node u.
  2. Sum of distance from node u to all other nodes excluding the nodes in the subtree rooted at vertex u
    This will be calculated with the help of dp2[v](sum of distance from node v to all other nodes in tree)

Sum of distance from node v to all other nodes excluding the subtree rooted at vertex v will be

From this we can calculate sum of distance from node u to all other nodes excluding the subtree rooted at vertex u by adding (N-sub[u]) to above equation, since N- sub[u] gives the total number of nodes excluding the nodes in the subtree rooted at vertex u.

Summing the above two parts we get:

This can be further simplified to:

Below is the implementation of above approach:

#include <bits/stdc++.h>
using namespace std;
 
// Function to perform the first depth-first search to
// calculate dp1 and sub values.
void dfs1(vector<vector<int> >& adj, vector<int>& dp1,
          vector<int>& dp2, vector<int>& sub, int curr,
          int p, int n)
{
    for (auto x : adj[curr]) {
        if (x != p) {
           
            // Recursively traverse the tree to calculate
            // dp1 and sub values.
            dfs1(adj, dp1, dp2, sub, x, curr, n);
 
            // Update dp1[curr] by adding dp1[x] and sub[x].
            dp1[curr] += dp1[x] + sub[x];
 
            // Update sub[curr] by adding sub[x].
            sub[curr] += sub[x];
        }
    }
  // Increment sub[curr] to account for the current node itself.
    sub[curr]++;
}
 
// Function to perform the second depth-first search to
// calculate dp2 values.
void dfs2(vector<vector<int> >& adj, vector<int>& dp1,
          vector<int>& dp2, vector<int>& sub, int curr,
          int p, int n)
{
    if (p != -1) {
        // Calculate dp2[curr] using dp2 from the parent and
        // sub values.
        dp2[curr] = (dp2[p] - sub[curr]) + (n - sub[curr]);
    }
    else {
      // For the root node, dp2 is equal to dp1.
        dp2[curr] = dp1[curr];
    }
 
    for (auto x : adj[curr]) {
        if (x != p) {
            // Recursively traverse the tree to calculate
            // dp2 values.
            dfs2(adj, dp1, dp2, sub, x, curr, n);
        }
    }
}
 
// Function to solve the problem and calculate the sum of
// distances for each node.
void solve(int n, vector<vector<int> >& edges)
{
    vector<vector<int> > adj(n + 1);
 
    // Create an adjacency list to represent the tree.
    for (int i = 0; i < n - 1; i++) {
        int x = edges[i][0];
        int y = edges[i][1];
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
 
    vector<int> dp1(n + 1, 0);
    vector<int> dp2(n + 1, 0);
    vector<int> sub(n + 1, 0);
 
    // Perform the first depth-first search to calculate dp1
    // and sub values.
    dfs1(adj, dp1, dp2, sub, 1, -1, n);
 
    // Perform the second depth-first search to calculate
    // dp2 values.
    dfs2(adj, dp1, dp2, sub, 1, -1, n);
 
    // Output the results for each node.
    for (int i = 1; i <= n; i++) {
        cout << dp2[i] << " ";
    }
}
 
// Driver Code
int main()
{
    int N = 5;
    vector<vector<int> > edges
        = { { 1, 2 }, { 1, 3 }, { 3, 4 }, { 3, 5 } };
    solve(N, edges);
}

                    
import java.util.ArrayList;
import java.util.List;
 
class TreeDistance {
    // Function to perform the first depth-first search to
    // calculate dp1 and sub values.
    static void dfs1(List<List<Integer>> adj, int[] dp1, int[] dp2,
                     int[] sub, int curr, int p, int n) {
        for (int x : adj.get(curr)) {
            if (x != p) {
                // Recursively traverse the tree to calculate
                // dp1 and sub values.
                dfs1(adj, dp1, dp2, sub, x, curr, n);
 
                // Update dp1[curr] by adding dp1[x] and sub[x].
                dp1[curr] += dp1[x] + sub[x];
 
                // Update sub[curr] by adding sub[x].
                sub[curr] += sub[x];
            }
        }
        // Increment sub[curr] to account for the current node itself.
        sub[curr]++;
    }
 
    // Function to perform the second depth-first search to
    // calculate dp2 values.
    static void dfs2(List<List<Integer>> adj, int[] dp1, int[] dp2,
                     int[] sub, int curr, int p, int n) {
        if (p != -1) {
            // Calculate dp2[curr] using dp2 from the parent and
            // sub values.
            dp2[curr] = (dp2[p] - sub[curr]) + (n - sub[curr]);
        } else {
            // For the root node, dp2 is equal to dp1.
            dp2[curr] = dp1[curr];
        }
 
        for (int x : adj.get(curr)) {
            if (x != p) {
                // Recursively traverse the tree to calculate
                // dp2 values.
                dfs2(adj, dp1, dp2, sub, x, curr, n);
            }
        }
    }
 
    // Function to solve the problem and calculate the sum of
    // distances for each node.
    static void solve(int n, List<List<Integer>> edges) {
        List<List<Integer>> adj = new ArrayList<>(n + 1);
 
        // Initialize the adjacency list.
        for (int i = 0; i <= n; i++) {
            adj.add(new ArrayList<>());
        }
 
        // Create an adjacency list to represent the tree.
        for (int i = 0; i < n - 1; i++) {
            int x = edges.get(i).get(0);
            int y = edges.get(i).get(1);
            adj.get(x).add(y);
            adj.get(y).add(x);
        }
 
        int[] dp1 = new int[n + 1];
        int[] dp2 = new int[n + 1];
        int[] sub = new int[n + 1];
 
        // Perform the first depth-first search to calculate dp1
        // and sub values.
        dfs1(adj, dp1, dp2, sub, 1, -1, n);
 
        // Perform the second depth-first search to calculate
        // dp2 values.
        dfs2(adj, dp1, dp2, sub, 1, -1, n);
 
        // Output the results for each node.
        for (int i = 1; i <= n; i++) {
            System.out.print(dp2[i] + " ");
        }
    }
 
    // Driver Code
    public static void main(String[] args) {
        int N = 5;
        List<List<Integer>> edges = List.of(
                List.of(1, 2),
                List.of(1, 3),
                List.of(3, 4),
                List.of(3, 5)
        );
        solve(N, edges);
    }
}

                    
# Function to perform the first depth-first search to
# calculate dp1 and sub values.
 
 
def dfs1(adj, dp1, dp2, sub, curr, p, n):
    for x in adj[curr]:
        if x != p:
            # Recursively traverse the tree to calculate dp1 and sub values.
            dfs1(adj, dp1, dp2, sub, x, curr, n)
 
            # Update dp1[curr] by adding dp1[x] and sub[x].
            dp1[curr] += dp1[x] + sub[x]
 
            # Update sub[curr] by adding sub[x].
            sub[curr] += sub[x]
 
    # Increment sub[curr] to account for the current node itself.
    sub[curr] += 1
 
# Function to perform the second depth-first search to
# calculate dp2 values.
 
 
def dfs2(adj, dp1, dp2, sub, curr, p, n):
    if p != -1:
        # Calculate dp2[curr] using dp2 from the parent and sub values.
        dp2[curr] = (dp2[p] - sub[curr]) + (n - sub[curr])
    else:
        # For the root node, dp2 is equal to dp1.
        dp2[curr] = dp1[curr]
 
    for x in adj[curr]:
        if x != p:
            # Recursively traverse the tree to calculate dp2 values.
            dfs2(adj, dp1, dp2, sub, x, curr, n)
 
# Function to solve the problem and calculate the sum of
# distances for each node.
 
 
def solve(n, edges):
    adj = [[] for _ in range(n + 1)]
 
    # Create an adjacency list to represent the tree.
    for edge in edges:
        x, y = edge
        adj[x].append(y)
        adj[y].append(x)
 
    dp1 = [0] * (n + 1)
    dp2 = [0] * (n + 1)
    sub = [0] * (n + 1)
 
    # Perform the first depth-first search to calculate dp1 and sub values.
    dfs1(adj, dp1, dp2, sub, 1, -1, n)
 
    # Perform the second depth-first search to calculate dp2 values.
    dfs2(adj, dp1, dp2, sub, 1, -1, n)
 
    # Output the results for each node.
    for i in range(1, n + 1):
        print(dp2[i], end=" ")
 
 
# Driver Code
if __name__ == "__main__":
    N = 5
    edges = [[1, 2], [1, 3], [3, 4], [3, 5]]
    solve(N, edges)

                    
using System;
using System.Collections.Generic;
 
class TreeDistanceSum
{
    // Function to perform the first depth-first search to
    // calculate dp1 and sub values.
    static void DFS1(List<List<int>> adj, List<int> dp1,
                     List<int> dp2, List<int> sub, int curr, int p, int n)
    {
        foreach (int x in adj[curr])
        {
            if (x != p)
            {
                // Recursively traverse the tree to calculate
                // dp1 and sub values.
                DFS1(adj, dp1, dp2, sub, x, curr, n);
 
                // Update dp1[curr] by adding dp1[x] and sub[x].
                dp1[curr] += dp1[x] + sub[x];
 
                // Update sub[curr] by adding sub[x].
                sub[curr] += sub[x];
            }
        }
        // Increment sub[curr] to account for the current node itself.
        sub[curr]++;
    }
 
    // Function to perform the second depth-first search to
    // calculate dp2 values.
    static void DFS2(List<List<int>> adj,
                     List<int> dp1, List<int> dp2, List<int> sub, int curr, int p, int n)
    {
        if (p != -1)
        {
            // Calculate dp2[curr] using dp2 from the parent and
            // sub values.
            dp2[curr] = (dp2[p] - sub[curr]) + (n - sub[curr]);
        }
        else
        {
            // For the root node, dp2 is equal to dp1.
            dp2[curr] = dp1[curr];
        }
 
        foreach (int x in adj[curr])
        {
            if (x != p)
            {
                // Recursively traverse the tree to calculate
                // dp2 values.
                DFS2(adj, dp1, dp2, sub, x, curr, n);
            }
        }
    }
 
    // Function to solve the problem and calculate the sum of
    // distances for each node.
    static void Solve(int n, List<List<int>> edges)
    {
        List<List<int>> adj = new List<List<int>>();
        for (int i = 0; i <= n; i++)
        {
            adj.Add(new List<int>());
        }
 
        // Create an adjacency list to represent the tree.
        foreach (List<int> edge in edges)
        {
            int x = edge[0];
            int y = edge[1];
            adj[x].Add(y);
            adj[y].Add(x);
        }
 
        List<int> dp1 = new List<int>(new int[n + 1]);
        List<int> dp2 = new List<int>(new int[n + 1]);
        List<int> sub = new List<int>(new int[n + 1]);
 
        // Perform the first depth-first search to calculate dp1
        // and sub values.
        DFS1(adj, dp1, dp2, sub, 1, -1, n);
 
        // Perform the second depth-first search to calculate
        // dp2 values.
        DFS2(adj, dp1, dp2, sub, 1, -1, n);
 
        // Output the results for each node.
        for (int i = 1; i <= n; i++)
        {
            Console.Write(dp2[i] + " ");
        }
    }
 
    // Driver Code
    static void Main()
    {
        int N = 5;
        List<List<int>> edges = new List<List<int>>
        {
            new List<int> { 1, 2 },
            new List<int> { 1, 3 },
            new List<int> { 3, 4 },
            new List<int> { 3, 5 }
        };
        Solve(N, edges);
    }
}

                    
// Function to perform the first depth-first search to
// calculate dp1 and sub values.
function dfs1(adj, dp1, dp2, sub, curr, p, n) {
    for (const x of adj[curr]) {
        if (x !== p) {
            // Recursively traverse the tree to calculate
            // dp1 and sub values.
            dfs1(adj, dp1, dp2, sub, x, curr, n);
 
            // Update dp1[curr] by adding dp1[x] and sub[x].
            dp1[curr] += dp1[x] + sub[x];
 
            // Update sub[curr] by adding sub[x].
            sub[curr] += sub[x];
        }
    }
    // Increment sub[curr] to account for the current node itself.
    sub[curr]++;
}
 
// Function to perform the second depth-first search to
// calculate dp2 values.
function dfs2(adj, dp1, dp2, sub, curr, p, n) {
    if (p !== -1) {
        // Calculate dp2[curr] using dp2 from the parent and
        // sub values.
        dp2[curr] = (dp2[p] - sub[curr]) + (n - sub[curr]);
    } else {
        // For the root node, dp2 is equal to dp1.
        dp2[curr] = dp1[curr];
    }
 
    for (const x of adj[curr]) {
        if (x !== p) {
            // Recursively traverse the tree to calculate
            // dp2 values.
            dfs2(adj, dp1, dp2, sub, x, curr, n);
        }
    }
}
 
// Function to solve the problem and calculate the sum of
// distances for each node.
function solve(n, edges) {
    const adj = Array.from({ length: n + 1 }, () => []);
 
    // Create an adjacency list to represent the tree.
    for (const edge of edges) {
        const [x, y] = edge;
        adj[x].push(y);
        adj[y].push(x);
    }
 
    const dp1 = Array(n + 1).fill(0);
    const dp2 = Array(n + 1).fill(0);
    const sub = Array(n + 1).fill(0);
 
    // Perform the first depth-first search to calculate dp1
    // and sub values.
    dfs1(adj, dp1, dp2, sub, 1, -1, n);
 
    // Perform the second depth-first search to calculate
    // dp2 values.
    dfs2(adj, dp1, dp2, sub, 1, -1, n);
 
    // Output the results for each node.
    for (let i = 1; i <= n; i++) {
        console.log(dp2[i] + " ");
    }
}
 
// Driver Code
const N = 5;
const edges = [[1, 2], [1, 3], [3, 4], [3, 5]];
solve(N, edges);

                    

Output
6 9 5 8 8 












Time Complexity: O(N)
Auxiliary Space: O(N)

Applications of Dynamic Programming on Trees:

Dynamic Programming (DP) on trees is a powerful technique for solving a variety of problems involving tree structures. Here is a comprehensive overview of its applications and considerations:

Practice Problems of DP on Trees

Diameter of N-ary Tree 

Maximum sum of the node values from root to any of the leaves without re-visiting any node

Maximum height of Tree when any Node can be considered as Root

Maximal Point Path

Related Article:


Article Tags :