Open In App

DP on Trees for Competitive Programming

Last Updated : 31 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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-tree

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?

  • Define the Problem: Clearly define the problem you want to solve in the context of a tree structure. Identify what information or value you need to compute or optimize within the tree.
  • Select a Root Node: Choose a suitable node as the root of the tree. This choice can significantly impact the efficiency of the DP approach. In many cases, the choice of the root node is arbitrary, but it should make the problem-solving process more straightforward.
  • Traversal Order: Decide on a traversal order, such as post-order, pre-order, or bottom-up, based on the problem’s nature and requirements. The traversal order dictates how you move through the tree and calculate solutions.
  • Define State and Recurrence Relations: Define the state for each node in the tree. The state represents the information needed to solve the problem for that node. Formulate recurrence relations that describe how to compute the solution for a node based on the solutions of its child nodes. This is where the DP magic happens, as you express how the solution at a given node depends on the solutions of its subproblems.
  • Initialize and Propagate DP Table: Initialize a DP table or array to store intermediate results. Use the chosen traversal order to propagate values through the tree, updating the DP table as you traverse. At each node, calculate and store the result for that node based on the recurrence relations.
  • Optimize: Optimize the calculation of subproblem solutions by avoiding redundant calculations. This often involves memoization, where you store and reuse previously computed results for nodes you’ve already processed.
  • Compute Final Result: Once you’ve traversed the entire tree and filled in the DP table, the final result is often found in the root node’s entry in the DP table. Depending on the problem, you may need to process and analyze the DP table further to extract the answer.

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.

dp[v][0] = \Sigma max(dp[u][0],dp[u][1])                             , 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.

dp[v][1]=max(1+dp[u][0] + dp[v][0]- max(dp[u][0],dp[u][1]))              , 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:

C++

#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);
}

                    

Java

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);
    }
}

                    

Python

# 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)

                    

C#

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);
    }
}

                    

Javascript

// 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?

  • Original Root: Start with the original root of the tree, where the tree is rooted. This is often an arbitrary choice or based on the problem’s context.
  • DFS (Depth-First Search): Use a depth-first search (DFS) to process the tree starting from the original root. During the DFS, calculate and store values for each node and its subtree rooted at that node. These values can be specific to the problem you’re solving. For example, you might calculate the sum of values in the subtree or the maximum value in the subtree.
  • Rerooting: After processing the entire tree with the original root, you can reroot the tree at any node. This means you can choose a different node in the tree to be the new root.
  • Updating Values: When you reroot the tree, you don’t need to perform a full DFS traversal again. Instead, you can update the values of the nodes and subtrees based on the values already calculated in the previous DFS traversal. This updating process is efficient and can be done in a way that avoids redundant computations.
  • Queries: Once you have updated the values for the new root, you can efficiently answer queries related to the values of nodes or subtrees in the tree. By rerooting the tree as needed, you can answer queries at different nodes without recomputing the values from scratch.

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:-

dp1[v]=\Sigma(dp1[u]+sub[u])                             , 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

dp2[v]-(dp1[u]+sub[u])

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:

dp2[u]=dp1[u] + (dp2[v]-(dp1[u]+sub[u])+(N-sub[u])

This can be further simplified to:

dp2[u]=dp2[v]+N-2*sub[u]

Below is the implementation of above approach:

C++

#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);
}

                    

Java

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);
    }
}

                    

Python3

# 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)

                    

C#

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);
    }
}

                    

Javascript

// 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:

  • Maximum/Minimum Path Sum: DP on trees is commonly used to find the maximum or minimum sum of a path from one node to another. This can be applied to problems like finding the longest or shortest path in a tree.
  • Diameter of a Tree: DP helps determine the diameter of a tree, which is the longest path between any two nodes. This involves finding the maximum distance in the tree.
  • Maximum Independent Set: DP can be used to find the maximum independent set of nodes in a tree. This is helpful in problems where you want to select nodes with certain constraints, such as no two adjacent nodes in the set.
  • Binary Trees: DP is often used in binary trees to solve problems like finding the maximum sum of values along a path or identifying the least common ancestor (LCA) of two nodes.
  • Binary Search Trees (BSTs): For binary search trees, DP can help find the minimum or maximum element in the tree, or check if a given tree is a valid BST.
  • Game Theory: DP on trees can be used to analyze games or decision-making processes represented as trees, helping to determine optimal strategies.
  • Subtree Problems: When you need to find the best solution within a subtree, DP can be applied to determine the maximum sum, product, or other properties of a subtree.
  • Dynamic Trees: DP is applicable to dynamic tree problems, where the structure of the tree changes over time with nodes being added or removed.
  • Graph Theory: Certain graph problems can be transformed into tree problems, making DP on trees a valuable technique for various graph algorithms.

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:



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads