Credits to FLEA for teaching me this trick.
Introduction
Recently I learnt an interesting trick I wanted to share with others. I'm not sure if this trick is well known, but I didn't know about it and didn't find any other articles on it so decided to write this blog.
Problem statement
We have a connected undirected graph with $$$n$$$ nodes and $$$m$$$ edges between them, each node having a value $$$w_i$$$ ($$$1 <= n, m <= 10^5$$$), ($$$1 <= w_i <= 10^9$$$).
Let's denote $$$f(a, b)$$$ as the minimum value of a node on a path from node $$$a$$$ to node $$$b$$$.
We have to answer $$$q$$$ queries about this graph. Each query contains $$$2$$$ nodes $$$a$$$, $$$b$$$ and asks for the maximum $$$f(a, b)$$$ over all possible paths from node $$$a$$$ to node $$$b$$$. ($$$1 <= q <= 10^5$$$).
Notes
This problem can be solved in another (more optimal) way, which is described in these comments: 1, 2. Thanks to valeriu and geniucos for mentioning it!
A similar idea from stefdasca.
Prerequisites
In order to fully understand the idea you have to know about DSU (disjoint set union) and small to large merging.
Solution
We will do some kind of DSU (disjoint set union). For each node, we don't only keep its parent, but also the queries it appears in (we store them in a map/set). Then we go through all the $$$n$$$ nodes in decreasing order of their values. When we are at a node — $$$u$$$, we "activate" it and then go through all the already activated neighbors the node has and we merge these two nodes.
How do we merge them?
First, we do the simple merge of the $$$2$$$ components with the classic $$$par[a] = b$$$. Then, we go through all queries the node is responsible for and check if it also appears in the query of the other node. If it does, we set the answer of that query to the weight of the node we are currently considering, since we know it will be the minimum on the path (because we are going through nodes in decreasing order of their values), if they don't both appear we just insert the the given query to the combined component and continue. But, since it would take too long, we merge the query sets with small to large merging (merge the node which has a smaller (in size) query set to the one that has a larger one). So, we do all this in $$$O((n + m) log^2n)$$$.
Some code
Note: ans[i] is the answer for query with index $$$i$$$.
First of all, we need the DSU functions — get (which returns the node responsible for the whole component) and union, which merges $$$2$$$ different components.
Since the only things we care about for a node are its parent and set of queries it's responsible for we can keep a pair of an integer and a map/set.
The union, as discussed in the above solution part involves small to large merging. If both nodes are responsible for a query then the answer for the query is the weight of the other node, otherwise we insert the query to the combined component.
After that, we need to set the initial components. Initially, nodes should have an empty query set and their parent should be themself only, and we can sort the nodes by decreasing order of their values in the same time (vector v will contain the node index on the second position). And, we can go also go through queries and add the queries responsible for a given node to a list.
Now for the iteration in decreasing order of the values, we keep the boolean activated for each node, which tells us whether the node was already "activated" (gone through) or not.
Then, we are left with outputting the answer for each query.
Variations
This trick can solve many variations of the problem, the most obvious one being that $$$f(a, b)$$$ would be the maximum value on the path instead of the minimum, which would require their little modifications
Thank you sir for sharing this trick.
based trick
SlavicG top contributor poggers
Thanks, SlavicG, with help of your blogs I can get better in cp!
Instead of just having weights on nodes, which is very uncomfortable, lets say the weight of and edge $$$e = (u, v)$$$ is $$$min(value[u], value[v])$$$ (because traversing it implies visiting both nodes). All that now remains do be done is to take the MST of this graph and the edge using with maximum $$$f(a,b)$$$ would be the one in the MST, because on this road, if there would've a more weight-y edge to use on the road from a to b, then that edge would've been considered first (because all edges from the MST are essential to having a path in the first place). Thus, construction of MST is $$$O(Mlogn)$$$ and queries are $$$O(Mlogn)$$$ with binary lifting.
IMO this wouldn't be the best application of this trick, and unfortunately every problem I've seen using a such trick could've always be solved otherwise.
I await highly the downvotes and explications for why I am wrong
You are right! The MST solution is correct but I thought the trick is still worth sharing. I will add the MST solution to the blog as well.
In any case, remembered some problems that could be solved using this idea:
IZhO18_plan (can be solved with paralel binary search)
The problem you described, but poorer quality because of matrices (can be solved with paralel binary search, and the MST thing, I guess)
This problem is also very similar: link
You can do the same by computing a MST where the cost of an edge $$$(u, v)$$$ is given by $$$\max(val_u, val_v)$$$. Then what you're interested in is just the maximum value on a path (which can be done online in $$$\log(N)$$$ per query in several ways. I thought it's worth mentioning because this is a less known (though not difficult to prove, especially by your construction) property of MST
Hi there, thanks for sharing this trick. In addition to this method and the $$$w = \min (val_u, val_v)$$$ method, I would also like to mention the connections between your DSU and Reachability Tree/Kruskal Reconstruction Tree. In fact, you are building the vertex-based reachability tree in your method as you solve the queries! It is well known that the maximum value on a path from $$$u$$$ to $$$v$$$ in the reachability tree is simply $$$\text{LCA}(u, v)$$$, so we can also solve the problem not by merging the queries but by simply outputting $$$\text{LCA}(u, v)$$$ for each query after the tree is constructed. This speeds the solution up to $$$\mathcal{O}((n+q)\log n)$$$ with binary lifting, for example.
Funnily, in his own contest this trick was employed to solve this problem
Another similar question CSAcademy Mountain Time
Yet another
treerelated problemorz
This code is pretty weird tbh.
Best trick ever!