Open In App

Optimal Construction of Roads

Last Updated : 20 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Given a connected graph with N nodes and N-1 bidirectional edges given by a 2D array edges[][], such that edges[i] = {u, v, w} states that there is a bidirectional edge between node u and node v with a weight of w. The task is to answer Q queries such that for each query, query[i] = {u, v, w} print “1” if adding a new edge between node u and node v with a weight w will lead to a better MST, else print “0”.

Note: All queries are independent of each other.

Examples:

Input: N = 5, edges[][] = {{1, 2, 2}, {1, 3, 3}, {3, 4, 5}, {3, 5, 4}}, Q = 2, query[][] = {{1, 4, 2}, {4, 5, 8}}
Output: 1 0
Explanation:

  • In the first query {1, 4, 2}, if we remove the edge from node 3 to node 4 and then add {1, 4, 2}, then we can reduce the total weight by 3 units.
  • In the second query, adding the edge {4, 5, 8} will not reduce the minimum cost of MST.

Input: N = 3, edges[][] = {{1, 2, 5}, {1, 3, 6}}, Q = 2, query[][] = {{2, 3, 2}, {2, 3, 12}}
Output: 1 0
Explanation:

  • In the first query, if we remove the edge from node 1 and 3 and then add the edge {2, 3, 2}, then we can reduce the total weight by 4 units.
  • In the second query, adding the edge {2, 3, 12} will not reduce the minimum cost of MST.

Approach: To solve the problem, follow the below idea:

Since in every query, we are given an edge and we need to find whether adding this new edge to the graph will reduce the cost of MST, so adding an edge will result in a cycle and in order to decrease the cost of MST, this new edge should have smaller weight as compared to maximum weighted edge among all the edges in the cycle.

Let’s say we add an edge between (u, v) with a weight w and node u and v have lca(u, v) as L, so to reduce the cost of MST, w < max weighted edge in the cycle. To find the max weighted edge in the cycle, we can find the max weighted edge from node L to node u and from node L to node v. The maximum node among both paths will be the max weighted edge in the cycle.

To find the lca and the maximum weighted edge efficiently for each query, we can use Binary Lifting to store the ancestors (LCA[][]) as well as the maximum weighted edge between node and ancestors (MWE[][]).

  • LCA[i][j] will store the (2 ^ j)th ancestor of node i
  • MWE[i][j] will store the maximum weighted edge from node i to (2 ^ j)th ancestor of node i.

In this way, we can find the LCA of two nodes and the maximum weighted edge in the cycle in log(N) time only.

Step-by-step algorithm:

  • Construct a table LCA[][], such that LCA[i][j] stores the (2 ^ j)th ancestor of node i.
  • Construct a table MWE[][], such that MWE[i][j] stores the weight of maximum weighted edge from node i to (2 ^ j)th ancestor of node i.
  • For each query, find the maximum weighted edge in the path u to LCA(u, v) and v to LCA(u, v) and then check if the weight is lesser or greater than the weight of new edge.
  • If the maximum weight is less than or equal to the weight of new edge, then adding the new edge won’t reduce the weight of MST. Otherwise, adding the new edge will reduce the weight of MST.

Below is the implementation of the algorithm:

C++
#include <bits/stdc++.h>
using namespace std;

int logN;

// function to construct the graph
void constructGraph(vector<vector<pair<int, int> > >& graph,
                    vector<vector<int> >& edges)
{
    for (auto edge : edges) {
        int u = edge[0];
        int v = edge[1];
        int wt = edge[2];
        graph[u].push_back({ v, wt });
        graph[v].push_back({ u, wt });
    }
}

// dfs function to initialize the find depth of every node
// and initialize the first column of LCA[][] and MWE[][]
// for every node
void dfs(int node, vector<vector<pair<int, int> > >& graph,
        int d, vector<int>& depth,
        vector<vector<int> >& LCA,
        vector<vector<int> >& MWE)
{
    // Initializing the depth of current node
    depth[node] = d;

    // Initialize the first column of LCA[][] and MWE[][]
    // for every node
    for (auto edge : graph[node]) {
        int childNode = edge.first;
        int weight = edge.second;

        // Check if the child node is not visited
        if (depth[childNode] == -1) {
            LCA[childNode][0] = node;
            MWE[childNode][0] = weight;
            dfs(childNode, graph, d + 1, depth, LCA, MWE);
        }
    }
}

// function to fill the LCA[][] table and MWE[][] table
void precompute(vector<vector<pair<int, int> > >& graph,
                vector<vector<int> >& LCA,
                vector<vector<int> >& MWE, int N)
{
    for (int j = 1; j < logN; j++) {
        for (int i = 0; i <= N; i++) {
            if (LCA[i][j - 1] != -1) {
                LCA[i][j] = LCA[LCA[i][j - 1]][j - 1];
                MWE[i][j] = max(MWE[i][j - 1],
                                MWE[LCA[i][j - 1]][j - 1]);
            }
        }
    }
}

// Function to find the maximum weight of an edge in the
// cycle
int solve(int u, int v, vector<int>& depth,
        vector<vector<int> >& LCA,
        vector<vector<int> >& MWE)
{
    if (depth[u] > depth[v])
        swap(u, v);

    // variable to store the maximum weight of an edge in
    // the cycle
    int ans = 0;

    // Binary Lifting
    for (int i = logN - 1; i >= 0; i--) {
        if (depth[LCA[u][i]] >= depth[v]) {
            ans = max(ans, MWE[u][i]);
            u = LCA[u][i];
        }
    }
    if (u == v)
        return ans;
    for (int i = logN - 1; i >= 0; i--) {
        if (LCA[u][i] != -1 && LCA[v][i] != -1
            && LCA[u][i] != LCA[v][i]) {
            ans = max(ans, max(MWE[u][i], MWE[v][i]));
            u = LCA[u][i];
            v = LCA[v][i];
        }
    }
    return max(ans, max(MWE[u][0], MWE[v][0]));
}

int main()
{

    // Input
    int N = 5;
    vector<vector<int> > edges = {
        { 1, 2, 2 }, { 1, 3, 3 }, { 3, 4, 5 }, { 3, 5, 4 }
    };
    int Q = 2;
    vector<vector<int> > query
        = { { 1, 4, 2 }, { 4, 5, 8 } };

    // adjacency list to store the graph
    vector<vector<pair<int, int> > > graph(N + 1);

    // function call to construct the graph
    constructGraph(graph, edges);

    // variable to store the number of columns in the table
    logN = log2(N) + 1;

    // vector to store the depth of all nodes
    vector<int> depth(N + 1, -1);
    // table to store the ancestors
    vector<vector<int> > LCA(N + 1, vector<int>(logN, -1));
    // table to store the maximum weighted edge from every
    // node to their ancestors
    vector<vector<int> > MWE(N + 1, vector<int>(logN, 0));

    dfs(1, graph, 1, depth, LCA, MWE);

    precompute(graph, LCA, MWE, N);

    for (int i = 0; i < Q; i++) {
        int u = query[i][0];
        int v = query[i][1];
        int wt = query[i][2];
        int maxWeight = solve(u, v, depth, LCA, MWE);
        if (maxWeight > wt) {
            cout << 1 << " ";
        }
        else {
            cout << 0 << " ";
        }
    }
    return 0;
}
Java
import java.util.*;

public class Main {
    static int logN;

    // Function to construct the graph
    static void constructGraph(ArrayList<ArrayList<int[]>> graph, int[][] edges) {
        for (int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            int wt = edge[2];
            graph.get(u).add(new int[]{v, wt});
            graph.get(v).add(new int[]{u, wt});
        }
    }

    // DFS function to initialize the find depth of every node
    // and initialize the first column of LCA[][] and MWE[][]
    // for every node
    static void dfs(int node, ArrayList<ArrayList<int[]>> graph, int d, int[] depth, int[][] LCA, int[][] MWE) {
        // Initializing the depth of current node
        depth[node] = d;

        // Initialize the first column of LCA[][] and MWE[][]
        // for every node
        for (int[] edge : graph.get(node)) {
            int childNode = edge[0];
            int weight = edge[1];

            // Check if the child node is not visited
            if (depth[childNode] == -1) {
                LCA[childNode][0] = node;
                MWE[childNode][0] = weight;
                dfs(childNode, graph, d + 1, depth, LCA, MWE);
            }
        }
    }

    // Function to fill the LCA[][] table and MWE[][] table
    static void precompute(ArrayList<ArrayList<int[]>> graph, int[][] LCA, int[][] MWE, int N) {
        for (int j = 1; j < logN; j++) {
            for (int i = 0; i <= N; i++) {
                if (LCA[i][j - 1] != -1) {
                    LCA[i][j] = LCA[LCA[i][j - 1]][j - 1];
                    MWE[i][j] = Math.max(MWE[i][j - 1], MWE[LCA[i][j - 1]][j - 1]);
                }
            }
        }
    }

    // Function to find the maximum weight of an edge in the cycle
    static int solve(int u, int v, int[] depth, int[][] LCA, int[][] MWE) {
        if (depth[u] > depth[v]) {
            int temp = u;
            u = v;
            v = temp;
        }

        // Variable to store the maximum weight of an edge in the cycle
        int ans = 0;

        // Binary Lifting
        for (int i = logN - 1; i >= 0; i--) {
            if (LCA[u][i] != -1 && depth[LCA[u][i]] >= depth[v]) {
                ans = Math.max(ans, MWE[u][i]);
                u = LCA[u][i];
            }
        }
        if (u == v)
            return ans;
        for (int i = logN - 1; i >= 0; i--) {
            if (LCA[u][i] != -1 && LCA[v][i] != -1 && LCA[u][i] != LCA[v][i]) {
                ans = Math.max(ans, Math.max(MWE[u][i], MWE[v][i]));
                u = LCA[u][i];
                v = LCA[v][i];
            }
        }
        return Math.max(ans, Math.max(MWE[u][0], MWE[v][0]));
    }

    public static void main(String[] args) {
        // Input
        int N = 5;
        int[][] edges = {
            {1, 2, 2}, {1, 3, 3}, {3, 4, 5}, {3, 5, 4}
        };
        int Q = 2;
        int[][] query = {
            {1, 4, 2}, {4, 5, 8}
        };

        // Adjacency list to store the graph
        ArrayList<ArrayList<int[]>> graph = new ArrayList<>();
        for (int i = 0; i <= N; i++) {
            graph.add(new ArrayList<>());
        }

        // Function call to construct the graph
        constructGraph(graph, edges);

        // Variable to store the number of columns in the table
        logN = (int) (Math.log(N) / Math.log(2)) + 1;

        // Vector to store the depth of all nodes
        int[] depth = new int[N + 1];
        Arrays.fill(depth, -1);

        // Table to store the ancestors
        int[][] LCA = new int[N + 1][logN];
        for (int[] row : LCA) {
            Arrays.fill(row, -1);
        }

        // Table to store the maximum weighted edge from every node to their ancestors
        int[][] MWE = new int[N + 1][logN];

        dfs(1, graph, 1, depth, LCA, MWE);

        precompute(graph, LCA, MWE, N);

        for (int i = 0; i < Q; i++) {
            int u = query[i][0];
            int v = query[i][1];
            int wt = query[i][2];
            int maxWeight = solve(u, v, depth, LCA, MWE);
            if (maxWeight > wt) {
                System.out.print(1 + " ");
            } else {
                System.out.print(0 + " ");
            }
        }
    }
}
Javascript
// Function to construct the graph
function constructGraph(graph, edges) {
    for (const edge of edges) {
        const [u, v, wt] = edge;
        graph[u].push([v, wt]);
        graph[v].push([u, wt]);
    }
}

// DFS function to initialize the find depth of every node
// and initialize the first column of LCA[][] and MWE[][]
// for every node
function dfs(node, graph, d, depth, LCA, MWE) {
    // Initializing the depth of the current node
    depth[node] = d;

    // Initialize the first column of LCA[][] and MWE[][]
    // for every node
    for (const [childNode, weight] of graph[node]) {
        // Check if the child node is not visited
        if (depth[childNode] === -1) {
            LCA[childNode][0] = node;
            MWE[childNode][0] = weight;
            dfs(childNode, graph, d + 1, depth, LCA, MWE);
        }
    }
}

// Function to fill the LCA[][] table and MWE[][] table
function precompute(graph, LCA, MWE, N, logN) {
    for (let j = 1; j < logN; j++) {
        for (let i = 0; i <= N; i++) {
            if (LCA[i][j - 1] !== -1) {
                LCA[i][j] = LCA[LCA[i][j - 1]][j - 1];
                MWE[i][j] = Math.max(MWE[i][j - 1], MWE[LCA[i][j - 1]][j - 1]);
            }
        }
    }
}

// Function to find the maximum weight of an edge in the cycle
function solve(u, v, depth, LCA, MWE, logN) {
    if (depth[u] > depth[v]) {
        [u, v] = [v, u];
    }

    // Variable to store the maximum weight of an edge in the cycle
    let ans = 0;

    // Binary Lifting
    for (let i = logN - 1; i >= 0; i--) {
        if (LCA[u][i] !== -1 && depth[LCA[u][i]] >= depth[v]) {
            ans = Math.max(ans, MWE[u][i]);
            u = LCA[u][i];
        }
    }

    if (u === v) {
        return ans;
    }

    for (let i = logN - 1; i >= 0; i--) {
        if (LCA[u][i] !== -1 && LCA[v][i] !== -1 && LCA[u][i] !== LCA[v][i]) {
            ans = Math.max(ans, Math.max(MWE[u][i], MWE[v][i]));
            u = LCA[u][i];
            v = LCA[v][i];
        }
    }

    return Math.max(ans, Math.max(MWE[u][0], MWE[v][0]));
}

// Main Function
function main() {
    // Input
    const N = 5;
    const edges = [
        [1, 2, 2], [1, 3, 3], [3, 4, 5], [3, 5, 4]
    ];
    const Q = 2;
    const queries = [
        [1, 4, 2], [4, 5, 8]
    ];

    // Adjacency list to store the graph
    const graph = Array.from({ length: N + 1 }, () => []);

    // Function call to construct the graph
    constructGraph(graph, edges);

    // Variable to store the number of columns in the table
    const logN = Math.floor(Math.log2(N)) + 1;

    // Vector to store the depth of all nodes
    const depth = Array(N + 1).fill(-1);

    // Table to store the ancestors
    const LCA = Array.from({ length: N + 1 }, () => Array(logN).fill(-1));

    // Table to store the maximum weighted edge from every node to their ancestors
    const MWE = Array.from({ length: N + 1 }, () => Array(logN).fill(0));

    dfs(1, graph, 1, depth, LCA, MWE);

    precompute(graph, LCA, MWE, N, logN);

    for (const query of queries) {
        const [u, v, wt] = query;
        const maxWeight = solve(u, v, depth, LCA, MWE, logN);
        process.stdout.write((maxWeight > wt ? 1 : 0) + ' ');
    }
}

// Call the main function
main();
Python3
import math

def construct_graph(graph, edges):
    for edge in edges:
        u, v, wt = edge
        graph[u].append((v, wt))
        graph[v].append((u, wt))

def dfs(node, graph, d, depth, LCA, MWE):
    depth[node] = d
    for child_node, weight in graph[node]:
        if depth[child_node] == -1:
            LCA[child_node][0] = node
            MWE[child_node][0] = weight
            dfs(child_node, graph, d + 1, depth, LCA, MWE)

def precompute(graph, LCA, MWE, N):
    for j in range(1, logN):
        for i in range(N + 1):
            if LCA[i][j - 1] != -1:
                LCA[i][j] = LCA[LCA[i][j - 1]][j - 1]
                MWE[i][j] = max(MWE[i][j - 1], MWE[LCA[i][j - 1]][j - 1])

def solve(u, v, depth, LCA, MWE):
    if depth[u] > depth[v]:
        u, v = v, u

    ans = 0

    for i in range(logN - 1, -1, -1):
        if depth[LCA[u][i]] >= depth[v]:
            ans = max(ans, MWE[u][i])
            u = LCA[u][i]

    if u == v:
        return ans

    for i in range(logN - 1, -1, -1):
        if LCA[u][i] != -1 and LCA[v][i] != -1 and LCA[u][i] != LCA[v][i]:
            ans = max(ans, max(MWE[u][i], MWE[v][i]))
            u = LCA[u][i]
            v = LCA[v][i]

    return max(ans, max(MWE[u][0], MWE[v][0]))

# Input
N = 5
edges = [
    [1, 2, 2],
    [1, 3, 3],
    [3, 4, 5],
    [3, 5, 4]
]
Q = 2
query = [
    [1, 4, 2],
    [4, 5, 8]
]

# Adjacency list to store the graph
graph = [[] for _ in range(N + 1)]

# Function call to construct the graph
construct_graph(graph, edges)

# Variable to store the number of columns in the table
logN = int(math.log2(N) + 1)

# Vector to store the depth of all nodes
depth = [-1] * (N + 1)

# Table to store the ancestors
LCA = [[-1] * logN for _ in range(N + 1)]

# Table to store the maximum weighted edge from every node to their ancestors
MWE = [[0] * logN for _ in range(N + 1)]

dfs(1, graph, 1, depth, LCA, MWE)

precompute(graph, LCA, MWE, N)

for i in range(Q):
    u, v, wt = query[i]
    max_weight = solve(u, v, depth, LCA, MWE)
    if max_weight > wt:
        print(1, end=" ")
    else:
        print(0, end=" ")


# This code is contributed by rambabuguphka

Output
1 0 


Time complexity: O(N * log(N) + Q * (logN)), where N is the number of nodes and Q is the number of queries.
Auxiliary Space: O(N * log(N)), because of LCA[][] and MWE[][] table.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads