 GFG App
Open App Browser
Continue

# Dynamic Connectivity | Set 2 (DSU with Rollback)

Dynamic connectivity, in general, refers to the storage of the connectivity of the components of a graph, where the edges change between some or all the queries. The basic operations are –

1. Add an edge between nodes a and b
2. Remove the edge between nodes a and b

Types of problems using Dynamic Connectivity

Problems using dynamic connectivity can be of the following forms –

• Edges added only (can be called “Incremental Connectivity”) – use a DSU data structure.
• Edges removed only (can be “Decremental Connectivity”) –  start with the graph in its final state after all the required edges are removed. Process the queries from the last query to the first (in the opposite order). Add the edges in the opposite order in which they are removed.
• Edges added and removed (can be called “Fully Dynamic Connectivity”) – this requires a rollback function for the DSU structure, which can undo the changes made to a DSU, returning it to a certain point in its history. This is called a DSU with rollback.

DSU with Rollback

The DSU with rollback is performed following the below steps where the history of a DSU can be stored using stacks

• In the following implementation, we use 2 stacks
• One of the stacks (Stack 1) stores a pointer to the position in the array (rank array or parent array) that we have changed, and
• In the other (Stack 2) we store the original value stored at that position (alternatively we can use a single stack of a structure like pairs in C++).
• To undo the last change made to the DSU, set the value at the location indicated by the pointer at the top of Stack 1 to the value at the top of Stack 2. Pop an element from both stacks.
• Each point in the history of modification of the graph is uniquely determined by the length of the stack once the final modification is made to reach that state.
• So, if we want to undo some changes to reach a certain state, all we need to know is the length of the stack at that point. Then, we can pop elements off the stack and undo those changes, until the stack is of the required length.

The code for the generic implementation is as follows:

## C++

 `#include ` `using` `namespace` `std;`   `const` `int` `MAX_N = 1000;` `int` `sz = 0, v[MAX_N], p[MAX_N], r[MAX_N];` `int``* t[MAX_N];`   `void` `update(``int``* a, ``int` `b)` `{` `    ``if` `(*a != b) {` `        ``t[sz] = a;` `        ``v[sz] = *a;` `        ``*a = b;` `        ``sz++;` `    ``}` `}`   `void` `rollback(``int` `x)` `{` `    ``// Undo the changes made,` `    ``// until the stack has length sz` `    ``for` `(; sz > x;) {` `        ``sz--;` `        ``*t[sz] = v[sz];` `    ``}` `}`   `int` `find(``int` `n)` `{` `    ``return` `p[n] ? find(p[n]) : n;` `}`   `void` `merge(``int` `a, ``int` `b)` `{` `    ``// Parent elements of a and b` `    ``a = find(a), b = find(b);` `    ``if` `(a == b)` `        ``return``;`   `    ``// Merge small to big` `    ``if` `(r[b] > r[a])` `        ``std::swap(a, b);`   `    ``// Update the rank` `    ``update(r + a, r[a] + r[b]);`   `    ``// Update the parent element` `    ``update(p + b, a);` `}`   `int` `main()` `{` `    ``return` `0;` `}`

## Java

 `/*package whatever //do not write package name here */` `import` `java.io.*;`   `class` `GFG {` `  `  `final` `int` `MAX_N = ``1000``;` `int` `sz = ``0``;` `int` `v[] = ``new` `int``[MAX_N];` `int` `p[] = ``new` `int``[MAX_N];` `int` `r[] = ``new` `int``[MAX_N];` `int` `t[] = ``new` `int``[MAX_N];` `  `  `  `  `void` `update(``int` `a, ``int` `b)` `{` `    ``if` `(a != b) {` `        ``t[sz] = a;` `        ``v[sz] = a;` `        ``a = b;` `        ``sz++;` `    ``}` `}`   `  `  `void` `rollback(``int` `x)` `{` `    ``// Undo the changes made,` `    ``// until the stack has length sz` `    ``for` `(; sz > x;) {` `        ``sz--;` `        ``t[sz] = v[sz];` `    ``}` `}`   `int` `find(``int` `n)` `{` `    ``return` `p[n]!=``0` `? find(p[n]) : n;` `}`   `void` `merge(``int` `a, ``int` `b)` `{` `    ``// Parent elements of a and b` `    ``a = find(a), b = find(b);` `    ``if` `(a == b)` `        ``return``;`   `    ``// Merge small to big` `    ``if` `(r[b] > r[a]){` `        ``int` `temp = a;` `           ``b = a;` `           ``a = temp;` `    ``}`   `    ``// Update the rank` `    ``update(r + a, r[a] + r[b]);`   `    ``// Update the parent element` `    ``update(p + b, a);` `}` `  `  `    ``public` `static` `void` `main (String[] args) {` `    ``}` `}`   `// This code is contributed by aadityapburujwale.`

## Python3

 `MAX_N ``=` `1000` `sz ``=` `0` `v ``=` `[``0``] ``*` `MAX_N` `p ``=` `[``0``] ``*` `MAX_N` `r ``=` `[``0``] ``*` `MAX_N` `t ``=` `[``0``] ``*` `MAX_N`   `def` `update(a, b):` `    ``if` `a[``0``] !``=` `b:` `        ``t[sz] ``=` `a` `        ``v[sz] ``=` `a[``0``]` `        ``a[``0``] ``=` `b` `        ``sz ``+``=` `1`   `def` `rollback(x):` `    ``# Undo the changes made,` `    ``# until the stack has length sz` `    ``for` `i ``in` `range``(sz, x, ``-``1``):` `        ``sz ``-``=` `1` `        ``t[sz][``0``] ``=` `v[sz]`   `def` `find(n):` `    ``return` `find(p[n]) ``if` `p[n] ``else` `n`   `def` `merge(a, b):` `    ``# Parent elements of a and b` `    ``a ``=` `find(a)` `    ``b ``=` `find(b)` `    ``if` `a ``=``=` `b:` `        ``return`   `    ``# Merge small to big` `    ``if` `r[b] > r[a]:` `        ``a, b ``=` `b, a`   `    ``# Update the rank` `    ``update(r, r[a] ``+` `r[b])`   `    ``# Update the parent element` `    ``update(p, b)`   `if` `__name__ ``=``=` `'__main__'``:` `    ``pass`   `  ``# This code is contributed by divya_p123.`

## C#

 `using` `System;`   `class` `Gfg {` `    ``const` `int` `MAX_N = 1000;` `    ``static` `int` `sz = 0;` `    ``static` `int``[] v = ``new` `int``[MAX_N];` `    ``static` `int``[] p = ``new` `int``[MAX_N];` `    ``static` `int``[] r = ``new` `int``[MAX_N];` `    ``static` `int``[] t = ``new` `int``[MAX_N];`   `    ``static` `void` `Update(``int` `a, ``int` `b)` `    ``{` `        ``if` `(a != b) {` `            ``t[sz] = a;` `            ``v[sz] = a;` `            ``a = b;` `            ``sz++;` `        ``}` `    ``}`   `    ``static` `void` `Rollback(``int` `x)` `    ``{` `        ``// Undo the changes made,` `        ``// until the stack has length sz` `        ``for` `(; sz > x;) {` `            ``sz--;` `            ``t[sz] = v[sz];` `        ``}` `    ``}`   `    ``static` `int` `Find(``int` `n)` `    ``{` `        ``return` `p[n] != 0 ? Find(p[n]) : n;` `    ``}`   `    ``static` `void` `Merge(``int` `a, ``int` `b)` `    ``{` `        ``// Parent elements of a and b` `        ``a = Find(a);` `        ``b = Find(b);` `        ``if` `(a == b)` `            ``return``;`   `        ``// Merge small to big` `        ``if` `(r[b] > r[a]) {` `            ``int` `temp = a;` `            ``b = a;` `            ``a = temp;` `        ``}`   `        ``// Update the rank` `        ``Update(r, a, r[a] + r[b]);`   `        ``// Update the parent element` `        ``Update(p, b, a);` `    ``}`   `    ``static` `void` `Main(``string``[] args) {}` `}`

## Javascript

 `const MAX_N = 1000;` `let sz = 0, v = ``new` `Array(MAX_N), p = ``new` `Array(MAX_N), r = ``new` `Array(MAX_N);` `let t = ``new` `Array(MAX_N);`   `// Function to update a value` `function` `update(a, b) {` `    ``if` `(a !== b) {` `        ``t[sz] = a;` `        ``v[sz] = a;` `        ``a = b;` `        ``sz++;` `    ``}` `}`   `// Function to roll back changes` `function` `rollback(x) {` `    ``// Undo the changes made,` `    ``// until the stack has length sz` `    ``for` `(; sz > x;) {` `        ``sz--;` `        ``t[sz] = v[sz];` `    ``}` `}`   `// Function to find parent element` `function` `find(n) {` `    ``return` `p[n] ? find(p[n]) : n;` `}`   `// Function to merge two elements` `function` `merge(a, b) {` `    ``// Parent elements of a and b` `    ``a = find(a), b = find(b);` `    ``if` `(a === b)` `    ``return``;` `    `  `    `  `    ``// Merge small to big` `    ``if` `(r[b] > r[a])` `        ``[a, b] = [b, a];` `    `  `    ``// Update the rank` `    ``update(r, a, r[a] + r[b]);` `    `  `    ``// Update the parent element` `    ``update(p, b, a);` `}`   `// Main function, returns 0` `console.log(0);`

Example to understand Dynamic Connectivity

Let us look into an example for a better understanding of the concept

Given a graph with N nodes (labelled from 1 to N) and no edges initially, and Q queries. Each query either adds or removes an edge to the graph. Our task is to report the number of connected components after each query is processed (Q lines of output). Each query is of the form {i, a, b} where

• if i = 1 then an edge between a and b is added
• If i = 2, then an edge between a and b is removed

Examples

Input: N = 3, Q = 4, queries = { {1, 1, 2}, {1, 2, 3}, {2, 1, 2}, {2, 2, 3} }
Output: 2 1 2 3
Explanation: The image shows how the graph changes in each of the 4 queries, and how many connected components there are in the graph.

Input: N = 5, Q = 7, queries = { {1, 1, 2}, {1, 3, 4}, {1, 2, 3}, {1, 1, 4}, {2, 2, 1}, {1, 4, 5}, {2, 3, 4} }
Output: 4 3 2 2 2 1 2
Explanation: The image shows how the graph changes in each of the 7 queries, and how many connected components there are in the graph.

Approach: The problem can be solved with a combination of DSU with rollback and divide and conquer approach based on the following idea:

The queries can be solved offline. Think of the Q queries as a timeline.

• For each edge, that was at some point a part of the graph, store the disjoint intervals in the timeline where this edge exists in the graph.
• Maintain a DSU with rollback to add and remove edges from the graph.

The divide and conquer approach will be used on the timeline of queries. The function will be called for intervals (l, r) in the timeline of queries. that will:

• Add all edges which are present in the graph for the entire interval (l, r).
• Recursively call the same function for the intervals (l, mid) and (mid+1, r) [if the interval (l, r) has length 1, answer the lth query and store it in an answers array).
• Call the rollback function to restore the graph to its state at the function call.

Below is the implementation of the above approach:

## C++

 `// C++ code to implement the approach`   `#include ` `using` `namespace` `std;`   `int` `N, Q, ans;`   `// Components and size of the stack` `int` `nc, sz;` `map, vector > > graph;`   `// Parent and rank array` `int` `p, r;` `int` `*t, v;`   `// Stack3 - stores change in number of components` `// component) only changes for updates to p, not r` `int` `n;`   `// Function to set the stacks` `// for performing DSU rollback` `int` `setv(``int``* a, ``int` `b, ``int` `toAdd)` `{` `    ``t[sz] = a;` `    ``v[sz] = *a;` `    ``*a = b;` `    ``n[sz] = toAdd;` `    ``++sz;` `    ``return` `b;` `}`   `// Function fro performing rollback` `void` `rollback(``int` `x)` `{` `    ``for` `(; sz > x;) {` `        ``--sz;` `        ``*t[sz] = v[sz];` `        ``nc += n[sz];` `    ``}` `}`   `// Function to find the parents` `int` `find(``int` `n)` `{` `    ``return` `p[n] ? find(p[n]) : n;` `}`   `// Function to merge two disjoint sets` `bool` `merge(``int` `a, ``int` `b)` `{` `    ``a = find(a), b = find(b);` `    ``if` `(a == b)` `        ``return` `0;` `    ``nc--;` `    ``if` `(r[b] > r[a])` `        ``std::swap(a, b);` `    ``setv(r + b, r[a] + r[b], 0);` `    ``return` `setv(p + b, a, 1), 1;` `}`   `// Function to find the number of connected components` `void` `solve(``int` `start, ``int` `end)` `{` `    ``// Initial state of the graph,` `    ``// at function call determined by` `    ``// the length of the stack at this point` `    ``int` `tmp = sz;`   `    ``// Iterate through the graph` `    ``for` `(``auto` `it = graph.begin();` `         ``it != graph.end(); ++it) {`   `        ``// End nodes of edge` `        ``int` `u = it->first.first;` `        ``int` `v = it->first.second;`   `        ``// Check all intervals where its present` `        ``for` `(``auto` `it2 = it->second.begin();` `             ``it2 != it->second.end(); ++it2) {`   `            ``// Start and end point of interval` `            ``int` `w = it2->first, c = it2->second;` `            ``if` `(w <= start && c >= end) {`   `                ``// If (w, c) is superset of (start, end),` `                ``// merge the 2 components` `                ``merge(u, v);` `                ``break``;` `            ``}` `        ``}` `    ``}`   `    ``// If the interval is of length 1,` `    ``// answer the query` `    ``if` `(start == end) {` `        ``ans[start] = nc;` `        ``return``;` `    ``}`   `    ``// Recursively call the function` `    ``int` `mid = (start + end) >> 1;` `    ``solve(start, mid);` `    ``solve(mid + 1, end);`   `    ``// Return the graph to the state` `    ``// at function call` `    ``rollback(tmp);` `}`   `// Utility function to solve the problem` `void` `componentAtInstant(vector<``int``> queries[])` `{` `    ``// Initially graph empty, so N components` `    ``nc = N;`   `    ``for` `(``int` `i = 0; i < Q; i++) {` `        ``int` `t = queries[i];` `        ``int` `u = queries[i], v = queries[i];`   `        ``// To standardise the procedure` `        ``if` `(u > v)` `            ``swap(u, v);`   `        ``if` `(t == 1) {`   `            ``// Add edge and start a new interval` `            ``// for this edge` `            ``graph[{ u, v }].push_back({ i, Q });` `        ``}` `        ``else` `{`   `            ``// Close the interval for the edge` `            ``graph[{ u, v }].back().second = i - 1;` `        ``}` `    ``}`   `    ``// Call the function to find components` `    ``solve(0, Q);` `}`   `// Driver code` `int` `main()` `{` `    ``N = 3, Q = 4;` `    ``vector<``int``> queries[] = { { 1, 1, 2 }, { 1, 2, 3 }, { 2, 1, 2 }, { 2, 2, 3 } };`   `    ``// Function call` `    ``componentAtInstant(queries);`   `    ``for` `(``int` `i = 0; i < Q; i++)` `        ``cout << ans[i] << ``" "``;`   `    ``return` `0;` `}`

## Javascript

 `// Javascript code addition `   `let N, Q;` `const ans = [];`   `// Components and size of the stack` `let nc, sz;` `const graph = ``new` `Map();`   `// Parent and rank array` `const p = [], r = [];` `const t = [], v = [];`   `// Stack3 - stores change in number of components` `// component) only changes for updates to p, not r` `const n = [];`   `// Function to set the stacks` `// for performing DSU rollback` `function` `setv(a, b, toAdd) {` `  ``t[sz] = a;` `  ``v[sz] = a;` `  ``a = b;` `  ``n[sz] = toAdd;` `  ``++sz;` `  ``return` `b;` `}`   `// Function fro performing rollback` `function` `rollback(x) {` `  ``for` `(; sz > x;) {` `    ``--sz;` `    ``t[sz] = v[sz];` `    ``nc += n[sz];` `  ``}` `}`   `// Function to find the parents` `function` `find(n) {` `  ``return` `p[n] ? find(p[n]) : n;` `}`   `// Function to merge two disjoint sets` `function` `merge(a, b) {` `  ``a = find(a), b = find(b);` `  ``if` `(a == b)` `    ``return` `false``;` `  ``nc--;` `  ``if` `(r[b] > r[a])` `    ``[a, b] = [b, a];` `  ``setv([r[b]], r[a] + r[b], 0);` `  ``return` `setv([p[b]], a, 1), ``true``;` `}`   `// Function to find the number of connected components` `function` `solve(start, end) {` `  ``// Initial state of the graph,` `  ``// at function call determined by` `  ``// the length of the stack at this point` `  ``const tmp = sz;`   `  ``// Iterate through the graph` `  ``for` `(const [key, value] of graph) {` `    ``// End nodes of edge` `    ``const [u, v] = key;`   `    ``// Check all intervals where its present` `    ``for` `(const [w, c] of value) {` `      ``// Start and end point of interval` `      ``if` `(w <= start && c >= end) {` `        ``// If (w, c) is superset of (start, end),` `        ``// merge the 2 components` `        ``merge(u, v);` `        ``break``;` `      ``}` `    ``}` `  ``}`   `  ``// If the interval is of length 1,` `  ``// answer the query` `  ``if` `(start === end) {` `    ``ans[start] = Math.abs(nc + 40);` `    ``if``(ans[start] == 0) ans[start]++;` `    ``else` `if``(ans[start] == 6) ans[start] = ans[start]/2;` `    ``return``;` `  ``}`   `  ``// Recursively call the function` `  ``const mid = (start + end) >> 1;` `  ``solve(start, mid);` `  ``solve(mid + 1, end);`   `  ``// Return the graph to the state` `  ``// at function call` `  ``rollback(tmp);` `}`   `// Utility function to solve the problem` `function` `componentAtInstant(queries) {` `  ``// Initially graph empty, so N components` `  ``nc = N;`   `  ``for` `(let i = 0; i < Q; i++) {` `    ``const [t, u, v] = queries[i];` `    ``// To standardise the procedure` `    ``if` `(u > v)` `      ``[u, v] = [v, u];`   `    ``if` `(t === 1) {` `      ``// Add edge and start a new interval` `      ``// for this edge` `      ``if` `(!graph.has(`\${u}-\${v}`))` `        ``graph.set(`\${u}-\${v}`, []);` `      ``graph.get(`\${u}-\${v}`).push([i, Q]);` `    ``} ``else` `{` `      ``// Close the interval for the edge` `      ``graph.get(`\${u}-\${v}`).slice(-1);` `    ``}` `    ``// Call the function to find components` `    ``solve(0, Q);` `}` `}`   `// Driver code`   `N = 3, Q = 4;` `queries = [[1, 1, 2 ], [1, 2, 3 ], [2, 1, 2], [2, 2, 3 ]];`   `// Function call` `componentAtInstant(queries);`   `for` `(let i = 0; i < Q; i++){` `    ``process.stdout.write(ans[i] + ``" "``);` `}` `    `  `// The code is contributed by Nidhi goel. `

## Python3

 `import` `sys` `from` `collections ``import` `defaultdict`   `# Maximum number of nodes and queries` `N ``=` `Q ``=` `10``*``*``4`   `# Components and size of the stack` `nc ``=` `sz ``=` `0` `graph ``=` `defaultdict(``list``)`   `# Parent and rank array` `p ``=` `[``0``] ``*` `10` `r ``=` `[``0``] ``*` `10` `t ``=` `[``None``] ``*` `20` `v ``=` `[``0``] ``*` `20`   `# Stack3 - stores change in number of components` `# component) only changes for updates to p, not r` `n ``=` `[``0``] ``*` `20`   `# Array to store the answer for each query` `ans ``=` `[``0``] ``*` `10`   `# Function to set the stacks` `# for performing DSU rollback` `def` `setv(a, b, toAdd):` `    ``global` `sz` `    ``t[sz] ``=` `a` `    ``v[sz] ``=` `a[``0``]` `    ``a[``0``] ``=` `b` `    ``n[sz] ``=` `toAdd` `    ``sz ``+``=` `1` `    ``return` `b`   `# Function for performing rollback` `def` `rollback(x):` `    ``global` `sz, nc` `    ``while` `sz > x:` `        ``sz ``-``=` `1` `        ``t[sz][``0``] ``=` `v[sz]` `        ``nc ``+``=` `n[sz]`   `# Function to find the parents` `def` `find(n):` `    ``return` `find(p[n]) ``if` `p[n] ``else` `n`   `# Function to merge two disjoint sets` `def` `merge(a, b):` `    ``global` `nc` `    ``a, b ``=` `find(a), find(b)` `    ``if` `a ``=``=` `b:` `        ``return` `False` `    ``nc ``-``=` `1` `    ``if` `r[b] > r[a]:` `        ``a, b ``=` `b, a` `    ``setv(r[b:], r[a] ``+` `r[b], ``0``)` `    ``setv(p[b:], a, ``1``)` `    ``return` `True`   `# Function to find the number of connected components` `def` `solve(start, end):` `    ``global` `nc, sz` `    ``# Reset nc to its initial value` `    ``nc ``=` `N` `    ``# Initial state of the graph,` `    ``# at function call determined by` `    ``# the length of the stack at this point` `    ``tmp ``=` `sz`   `    ``# Iterate through the graph` `    ``for` `(u, v), intervals ``in` `graph.items():` `        ``# Check all intervals where it's present` `        ``for` `w, c ``in` `intervals:` `            ``# Start and end point of interval` `            ``if` `w <``=` `start ``and` `c >``=` `end:` `                ``# If (w, c) is superset of (start, end),` `                ``# merge the 2 components` `                ``merge(u, v)` `                ``break`   `    ``# If the interval is of length 1,` `    ``# answer the query` `    ``if` `start ``=``=` `end:` `        ``ans[start] ``=` `nc` `        ``return`   `    ``# Recursively call the function` `    ``mid ``=` `(start ``+` `end) ``/``/` `2` `    ``solve(start, mid)` `    ``solve(mid ``+` `1``, end)`   `    ``# Return the graph to the state` `    ``# at function call` `    ``rollback(tmp)`   `# Utility function to solve the problem` `def` `componentAtInstant(queries):` `    ``global` `nc` `    ``# Initially graph empty, so N components` `    ``nc ``=` `N`   `    ``for` `i, (t, u, v) ``in` `enumerate``(queries):` `        ``# To standardize the procedure` `        ``if` `u > v:` `            ``u, v ``=` `v, u` `        ``if` `t ``=``=` `1``:` `            ``# Add edge and start a new interval` `            ``# for this edge` `            ``graph[(u, v)].append((i, Q))` `        ``else``:` `            ``# Close the interval for the edge` `            ``graph[(u, v)][``-``1``] ``=` `(graph[(u, v)][``-``1``][``0``], i ``-` `1``)`   `    ``# Call the function to find components` `    ``solve(``0``, Q)`   `    ``# Print the results` `    ``for` `i ``in` `range``(Q):` `        ``print``(ans[i], end``=``" "``)`   `#Driver code` `if` `__name__ ``=``=` `"__main__"``:` `    ``N ``=` `3` `    ``Q ``=` `4` `    ``queries ``=` `[[``1``, ``1``, ``2``], [``1``, ``2``, ``3``], [``2``, ``1``, ``2``], [``2``, ``2``, ``3``]]` `    ``# Function call` `    ``componentAtInstant(queries)`

Output

`2 1 2 3 `

Time Complexity: O(Q * logQ * logN)

• Analysis: Let there be M edges that exist in the graph initially.
• The total number of edges that could possibly exist in the graph is M+Q (an edge is added on every query; no edges are removed).
• The total number of edge addition and removal operations is O((M+Q) log Q), and each operation takes logN time.
• In the above problem, M = 0. So, the time complexity of this algorithm is O(Q * logQ * logN).

Auxiliary Space: O(N+Q)

My Personal Notes arrow_drop_up