This blog is about maintaining mst with online edge insertions (or msf — minimum spanning forest to be exact) by using a data structure I came up with in $$$O(\log n)$$$ for edge insertion. although this problem can solved quite easily for anyone who knows LCT (link-cut tree), coding LCT is not really practical and has really bad constants. The data structure I present has very good constants because as you'll see all it does is manipulation on arrays. For obvious reasons that will be shown later I have called this data structure "weighted DSU".
Warmup problem
The problem is stated as follows: you are given an undirected graph where each edge has a time $$$t_i$$$. you are asked to answer queries of the form "what is the smallest time t such that you can get between u and v with all edges in time $$$ \lt = t$$$". This problem is analogous to calculating the msf of the graph where the weights are the times and being able to answer what is the maximum on the simple path between u and v. This problem can be solved a lot of different ways, I will present the dsu solution:
first we want to sort the edges by time/weight, than we will add the edges in a dsu like way but without path compression. Additionally we we will store on each edge in the dsu the weight of the edges it represents. To be specific we will store 3 arrays: parent, weight and size. when connecting two representatives u to v we update $$$parent[u] = v, weight[u] = w, size[v] += size[u]$$$. now we can find the representative of v at time w. One way to answer the queries will be to binary search on the answer, which will be $$$O(\log^2 n)$$$. A faster way is to essentially find the LCA of u and v in the dsu tree. this can be done by moving up with the vertex with current smallest out-edge weight. This will be $$$O(\log n)$$$.
The data structure
We will use the same idea from the warmup problem and try to turn it to a dynamic one. When adding an edge we want to think what would have happened if we had added the edges in increasing order, this will result in the following almost complete algorithm:
when adding an edge with weight w between u and v we can get the root of u at time w, get the root of v at time w and connect the one with smaller subtree size to the bigger. assume we connect u to v, this means we discarded the out-edge of u. To correct this we will recursively add the edge between u and parent[u] with weight weight[u] (adding the edge we deleted).
This algorithm has 3 problems:
1. The underlying increasing nature of the weight of the edges going up was what our original DS was relying on, now we may destroy it.
2. the array size is dynamic so we cant rely on it like we did in the warmup problem.
3. what if the vertices are already connected?
The first problem has a straightforward solution — when trying to access the parent of a vertex, jump over all the edges that have weight less than the edge to the parent and connect the vertex to the new parent. The get root function will now look like this:
int getRoot(int v, int w = INF - 1) {
while (weight[v] <= w) {
while (weight[parent[v]] <= weight[v])
parent[v] = parent[parent[v]];
v = parent[v];
}
return v;
}
The second problem can be solved in an elegant and simple way — instead of connecting by size connect by a random index! To be specific we create an array index that is initialized with random values. We connect the vertex with smaller index to the one with the bigger index. (this union technique is known to be with same expected complexity as union by size which is optimal complexity)
To solve the third problem we can use mst properties. when adding an edge {w, u, v} we find the "main" edge along the path between u and v — the edge with maximum weight (or an edge if there are multiple). if it is weight is bigger than w, than we delete it and add the edge with the algorithm described above. Putting all of this together we get the following DS:
Time complexity
While I do not have a formal proof for the time complexity, I can provide a rough sketch/intuition for it:
what our DS does is it tries to replicate the DS from the warmup problem. It does that pretty good but has minor defects as described in problem 2. My claim is that this defects constitute at most the depth of the tree in the warmup problem, which will still result in max depth of $$$O(\log n)$$$. Testing that I had done highly support this claim. If anyone has a formal proof please share it!
another advantage of this DS are the constants — while in theory LCT has $$$O(\log n)$$$ complexity, it has really bad constants and this data structure easily defeats it often time being 3-6 times faster.
Additional operations
Deleting maximal edge:
Assume that all weights in the graph are distinct. We will notice that the maximal edge in each connected component is necessary — it is a bottleneck edge. Therefore we can delete the maximal edge and it will break the component into two components. Deleting the edge wont create a problem because no other edge addition used it to "jump" up. The easiest way to deal with the problem if the weights are not distinct is to just make them distinct.
Saving connected component size:
For some problems we may want to maintain the size of each connected component in the msf, even after edge deletions. We can do this buy maintaining the subtree size of each node in the tree that is saved in our DS. that is, we maintain an array size such that for each node v size[v] equals the number of nodes are there such that v is a parent of them. The easiest and most elegant way I found doing it was to change the size array as if the the edges in the parent path from u and v were disconnected and when going through an edge add the size back.
Problems
Comment if you can think of more uses of this DS









Cool!
Not only the solution,there are already other $$$O(n\log n)$$$ solution:
vectorto store queries including nodes in the set and use heristic merging to solve it offline.Is it the same algorithm described in this paper? FWIW, the paper was published one year after the blog post.
As the first author, I believe the idea in our paper is quite different from the blog post.
The key difference is that our data structure, the AM-tree, is not randomized. It relies on the size[] array to keep the tree size-balanced. Specifically, we require that size[u] <= 2/3 size[par[u]] for any node u. The Am-tree guarantees an O(logn) amortized cost per update and strict O(logn) tree height. You can find the formal proof in section 4 of our paper.
We saw this blog post after we had finished writing our paper. However, the weightedDSU described here requires a random total order, and the formal proof of the time complexity is not provided. Actually this approach is more like the OEC-Forest described in "On Querying Historical Connectivity in Temporal Graphs" (https://dl.acm.org/doi/10.1145/3654960 ), which was published in early 2024.
Related: the authors of the OEC-Forest paper seem to have published a new paper "Minimum Spanning Tree Maintenance in Dynamic Graphs". Sadly, I can't seem to find either on Google Scholar, so unless the authors share it, it will be locked behind a paywall.
PS: your link is broken.
I see, thank you for the clarification! (Just to be clear -- I was not making an accusation, as coincidence always happens)
Btw it is a pity that this post is ignored. There aren't many posts (if any) in CF that actually "invents" something. Admittedly, the DS is somewhat specific and can't do many general operations, but this is certainly a novel invention.
I actually knew this post right after it was published but didn't commented anything, because the post was not easy to understand and I wasn't sure of its correctness :(
You can add it to the Catalog!
That's a great idea!
about it being ignored, maybe there needs to be some changes to the recent actions — as I look at recent actions almost everyday, but I completely missed this post, not sure how :(
To be clear, the author was grey when writing this post.
Just give my two cents. Could the algorithm be extended to solve incremental minimum cut problem? The concept of the algorithm is somehow similar with Gusfield's algorithm for constructing minimum cut tree, since they are both maintaining a tree/forest dynamically.
By the way for what its worth, I came up with the same idea while solving a different problem. But that was five years ago.
https://www.infoarena.ro/job_detail/2603558?action=view-source
code isnt optimal, but back then this was the fastest solution on this problem. I had tried proving time complexity here but had failed after some discussion with bicsi. Maybe you can add this problem as well to the list of problems in your post.
While I didnt particularly write a blogpost/paper about this, I did have some more work done on similar types of dynamic trees which can answer more fancy queries related to edge bottlenecks on static and incremental edge additions in graphs, apart from just finding the MST.