Divide and Combine Tree
On Segments
We start with a neat problem:
For a permutation of \(1-n\), we call a value-contiguous interval a segment. Find the number of segments in a permutation. For example, \(\{5 ,3 ,4, 1 ,2\}\) has segments: \([1,1],[2,2],[3,3],[4,4],[5,5],[2,3],[4,5],[1,3],[2,5],[1,5]\).
Looking at this, it seems we need to maintain the value set of intervals, which seems quite complex. Segment trees can query whether an interval is a segment, but they aren't great for counting segments.
Here we introduce this magical data structure—the Divide-Combine Tree!
Continuous Segments
Before introducing the Divide-Combine Tree, we first set some prerequisite conditions. Since the definitions given in LCA's course materials are not easy to understand, for the reader's convenience, we give some less rigorous (but easier to understand) definitions.
Permutations and Continuous Segments
Permutation: Define an \(n\)-order permutation \(P\) as a sequence of size \(n\) such that \(P_i\) takes all values \(1,2,\cdots,n\). More formally, an \(n\)-order permutation \(P\) is an ordered set satisfying:
- \(|P|=n\).
- \(\forall i,P_i\in[1,n]\).
-
\(\nexists i,j\in[1,n],P_i=P_j\).
Continuous Segment: For a permutation \(P\), define a continuous segment \((P,[l,r])\) as an interval \([l,r]\) such that the value range of \(P_{l\sim r}\) is contiguous. More formally, for permutation \(P\), a continuous segment is an interval \([l,r]\) satisfying:
In particular, when \(l>r\), we consider this an empty continuous segment, denoted as \((P,\varnothing)\).
We denote the set of all continuous segments of permutation \(P\) as \(I_P\), and we consider \((P,\varnothing)\in I_P\).
Operations on Continuous Segments
Continuous segments are defined based on intervals and value ranges, so we can define intersection, union, and difference operations on them.
Define \(A=(P,[a,b]),B=(P,[x,y])\), and \(A,B\in I_P\). Then the relations and operations on continuous segments can be expressed as:
- \(A\subseteq B\iff x\le a\wedge b\le y\).
- \(A=B\iff a=x\wedge b=y\).
- \(A\cap B=(P,[\max(a,x),\min(b,y)])\).
- \(A\cup B=(P,[\min(a,x),\max(b,y)])\).
- \(A\setminus B=(P,\{i|i\in[a,b]\wedge i\notin[x,y]\})\).
Essentially, these operations are just ordinary set intersection, union, and difference applied to intervals.
Properties of Continuous Segments
Some obvious properties of continuous segments. Define \(A,B\in I_P,A \cap B \neq \varnothing,A \notin B,B \notin A\), then we have \(A\cup B,A\cap B,A\setminus B,B\setminus A\in I_P\).
Proof? The essence of the proof is just set intersection, union, and difference operations.
Divide-Combine Tree
Alright, now we get to the main point. You might have guessed it: the Divide-Combine Tree is exactly a tree composed of continuous segments. But note that a permutation can have up to \(O(n^2)\) continuous segments, so we need to extract the more fundamental continuous segments to form the Divide-Combine Tree.
Primitive Segments
Actually, the full name of this definition is primitive continuous segment. But the author believes "primitive segment" is more concise.
For a permutation \(P\), we consider a primitive segment \(M\) to be one such that in the set \(I_P\), there exists no continuous segment that intersects it but does not contain it. Formally, we consider \(X\in I_P\) satisfying \(\forall A\in I_P,\ X\cap A= (P,\varnothing)\vee X\subseteq A\vee A\subseteq X\).
The set of all primitive segments is \(M_P\). Obviously, \((P,\varnothing)\in M_P\).
Clearly, primitive segments are either disjoint or have an inclusion relationship. And you'll find that a continuous segment can be composed of several disjoint primitive segments. The largest primitive segment is the entire permutation itself, which contains all other primitive segments. Therefore, we consider that primitive segments can form a tree structure, which we call the Divide-Combine Tree. More strictly, the Divide-Combine Tree of permutation \(P\) consists of all primitive segments of permutation \(P\).
After all these definitions, we need some diagrams. Consider permutation \(P=\{9,1,10,3,2,5,7,6,8,4\}\). Its Divide-Combine Tree formed by primitive segments is as follows:

In the diagram, we did not label the primitive segments. Each node in the diagram represents a primitive segment. We only labeled the value range of each primitive segment. For example, node \([5,8]\) represents the primitive segment \((P,[6,9])=\{5,7,6,8\}\). So here comes a question: What are divide nodes and combine nodes?
Divide Nodes and Combine Nodes
Here we give the definitions directly, and discuss their correctness later.
- Value Range Interval: For a node \(u\), denote its value range interval as \([u_l,u_r]\).
- Child Sequence: For a node \(u\) on the Divide-Combine Tree, assume its child nodes form an ordered sequence, where the elements are value range intervals (a single number \(x\) can be understood as interval \([x,x]\)). We call this sequence the child sequence. Denote it as \(S_u\).
- Child Permutation: For a child sequence \(S_u\), after discretizing its elements into positive integers, the resulting permutation is called the child permutation. For example, for node \([5,8]\), its child sequence is \(\{[5,5],[6,7],[8,8]\}\). After sorting the intervals and labeling them, its child permutation becomes \(\{1,2,3\}\); similarly, the child permutation of node \([4,8]\) is \(\{2,1\}\). Denote the child permutation of node \(u\) as \(P_u\).
- Combine Node: We consider nodes whose child permutation is either increasing or decreasing as combine nodes. Formally, nodes satisfying \(P_u=\{1,2,\cdots,|S_u|\}\) or \(P_u=\{|S_u|,|S_u-1|,\cdots,1\}\) are called combine nodes. Leaf nodes have no child permutation, but we also consider them as combine nodes.
- Divide Node: Nodes that are not combine nodes are divide nodes.
From the diagram, we can see that only \([1,10]\) is not a combine node. Because the child permutation of \([1,10]\) is \(\{3,1,4,2\}\).
Properties of Divide Nodes and Combine Nodes
The naming of divide and combine nodes comes from their properties. First, we have a very obvious property: For any node \(u\) in the Divide-Combine Tree, the union of its child sequence intervals is exactly the value range interval of node \(u\). That is, \(\bigcup_{i=1}^{|S_u|}S_u[i]=[u_l,u_r]\).
For a combine node \(u\): any subinterval of its child sequence forms a continuous segment. Formally, \(\forall S_u[l\sim r]\), we have \(\bigcup_{i=l}^rS_u[i]\in I_P\).
For a divide node \(u\): any subinterval of length greater than 1 (here length refers to the number of elements in the child sequence, not the length of the index interval) of its child sequence does not form a continuous segment. Formally, \(\forall S_u[l\sim r],l<r\), we have \(\bigcup_{i=l}^rS_u[i]\notin I_P\).
The property of combine nodes is not hard to prove. Because the child permutation of a combine node is either increasing or decreasing, and the value range intervals are end-to-end connected, any contiguous subsequence (interval) is a continuous segment.
For the property of divide nodes, many readers might not understand: why do any subintervals of length greater than \(1\) not form a continuous segment?
Use proof by contradiction. Assume for a node \(u\), there exists a longest interval \(S_u[l\sim r]\) in its child sequence that forms a continuous segment. Then \(A=\bigcup_{i=l}^rS_u[i]\in I_P\), which means \(A\) is a primitive segment! (Because \(A\) is the longest in the child sequence, so we cannot find a continuous segment that intersects it but does not contain it.) Then you did not use all primitive segments to construct this Divide-Combine Tree. Contradiction.
Construction of the Divide-Combine Tree
For the specific construction of the Divide-Combine Tree, LCA provides a linear construction algorithm1. Below we present a more understandable \(O(n\log n)\) algorithm.
Incremental Method
We consider the incremental method. Use a stack to maintain the Divide-Combine Forest formed by the first \(i-1\) elements. Here we need to emphasize that Divide-Combine Forest means at any time, nodes in the stack are either divide nodes or combine nodes. Now consider the current node \(P_i\).
- First, we check if it can become a child of the top node of the stack. If yes, make it a child of the top node, then pop the top node and set it as the current node. Repeat this process until the stack is empty or it cannot become a child of the top node.
- If it cannot become a child of the top node, check if we can merge several consecutive nodes at the top of the stack into one node (the method to determine whether merging is possible is described later). Take the merged node as the current node.
- Repeat the above process until no further operations are possible. Then end this increment and directly push the current node onto the stack.
Next, we explain in detail.
Specific Strategy
We believe that if the current point can become a child of the top node, then the top node is a combine node. If it were a divide node, then after merging, this divide node would have a sub-continuous segment, violating the property of divide nodes. Therefore, it must be a combine node.
If it cannot become a child of the top node, then we check if several consecutive nodes at the top of the stack can merge with the current point. Let \(l\) be the left endpoint of the interval where the current point lies. We compute \(L_i\) as the maximum left endpoint \(< l\) among continuous segments with right endpoint index \(i\). The current node is \(P_i\), and the top node of the stack is denoted as \(t\).
- If \(L_i\) does not exist, then obviously the current node cannot merge;
- If \(t_l=L_i\), then these two nodes merge, and after merging, it becomes a combine node;
- Otherwise, there must exist a node \(t'\) in the stack whose left endpoint \({t'}_l=L_i\), then we can merge from the current node to \(t'\) to form a divide node;
Determining Whether Merging is Possible
Finally, we consider how to handle \(L_i\). In fact, a continuous segment \((P,[l,r])\) is equivalent to the interval range (max-min) being equal to the interval length minus 1. That is
Moreover, since P is a permutation, for any interval \([l,r]\) we have
Thus we maintain \(\max_{l\le i\le r}P_i-\min_{l\le i\le r}P_i-(r-l)\), then finding a continuous segment is equivalent to querying a minimum value!
With the above idea, it's not hard to come up with such an algorithm. For the current \(i\) during the incremental process, we maintain an array \(Q\) representing the range minus length for interval \([j,i]\). That is
Now we want to know if there exists a smallest \(j\) in \(1\sim i-1\) such that \(Q_j=0\). This is equivalent to finding the minimum value of \(Q_{1\sim i-1}\). The smallest \(j\) found is \(L_i\). If none exists, then \(L_i=i\).
But when the \(i\)-th increment ends, we need to quickly update the \(Q\) array to the situation of i+1. The original interval changes from \([j,i]\) to \([j,i+1]\). If \(P_{i+1}>\max\) or \(P_{i+1}<\min\), \(Q_j\) will change. How does it change? If \(P_{i+1}>\max\), it's equivalent to subtracting \(\max\) from \(Q_j\) and then adding \(P_{i+1}\) to complete the update of \(Q_j\); similarly for \(P_{i+1}<\min\), it's equivalent to \(Q_j=Q_j+\min-P_{i+1}\).
Then what if for an interval \([x,y]\), the interval \(\max\) of \(P_{x\sim i},P_{x+1\sim i},P_{x+2\sim i},\cdots,P_{y\sim i}\) are all the same? You've discovered that this is equivalent to performing a range addition operation; similarly, when the interval \(\min\) of \(P_{x\sim i},P_{x+1\sim i},\cdots,P_{y\sim i}\) are all the same, it's also a range addition operation. Meanwhile, the updates of \(\max\) and \(\min\) are independent, so they can be updated separately.
Therefore, our maintenance of \(Q\) can be described as follows:
- Find the largest \(j\) such that \(P_{j}>P_{i+1}\). Then obviously, all numbers in \(P_{j+1\sim i}\) are less than \(P_{i+1}\), so we need to update the maximum value for \(Q_{j+1\sim i}\). Since \(P_{i},\max(P_i,P_{i-1}),\max(P_i,P_{i-1},P_{i-2}),\cdots,\max(P_i,P_{i-1},\cdots,P_{j+1})\) is (non-strictly) monotonically increasing, we can perform the same update for each segment with the same \(\max\), i.e., a range addition operation.
- Updating \(\min\) is similar.
- Subtract \(1\) from every \(Q_j\). Because the interval length increases by \(1\).
- Query \(L_i\): i.e., query the index where the minimum value of \(Q\) is located.
Yes, we can use a segment tree to maintain \(Q\)! Now there's another problem: how to find the same segment where their \(\max/\min\) are all the same? Use monotonic stacks! Maintain two monotonic stacks for \(\max\) and \(\min\) respectively. Then obviously, for intervals between adjacent elements in the stack, the \(\max/\min\) are the same. So while maintaining the monotonic stacks, we can update the segment tree accordingly.
See the code for specific maintenance methods.
After so much dry explanation, readers might be confused. So let's look at a diagram first. Long image warning!

Implementation
Finally, here is a reference implementation. The code is adapted from 大米饼's blog, with some comments added.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
References and Links
-
Liu Cheng'ao. Simple Continuous Segment Data Structure. WC2019 Camp Exchange. ↩
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用