Loading docs/ds/divide-combine.md +15 −17 Original line number Diff line number Diff line 解释一下本文可能用到的符号:$\wedge$ 逻辑与,$\vee$ 逻辑或。 ## 关于段的问题 我们由一个小清新的问题引入: > 对于一个 1-n 的排列,我们称一个值域连续的区间为段。问一个排列的段的个数。比如,$\{5 ,3 ,4, 1 ,2\}$ 的段有:[1,1],[2,2],[3,3],[4,4],[5,5],[2,3],[4,5],[1,3],[2,5],[1,5]。 > 对于一个 1-n 的排列,我们称一个值域连续的区间为段。问一个排列的段的个数。比如,$\{5 ,3 ,4, 1 ,2\}$ 的段有:$[1,1],[2,2],[3,3],[4,4],[5,5],[2,3],[4,5],[1,3],[2,5],[1,5]$。 看到这个东西,感觉要维护区间的值域集合,复杂度好像挺不友好的。线段树可以查询某个区间是否为段,但不太能统计段的个数(也可能是因为我太菜了不会用线段树) Loading @@ -30,9 +28,9 @@ $$ (\nexists\ x,z\in[l,r],y\notin[l,r],\ P_x<P_y<P_z) $$ 特别地,当 $l>r$ 时,我们认为这时一个空的连续段,记作 $(P,\varnothing)$。 特别地,当 $l>r$ 时,我们认为这是一个空的连续段,记作 $(P,\varnothing)$。 我们称排列 P 的所有连续段的集合为 $I_P$. 并且我们认为 $(P,\varnothing)\in I_P$. 我们称排列 P 的所有连续段的集合为 $I_P$,并且我们认为 $(P,\varnothing)\in I_P$。 ### 连续段的运算 Loading @@ -43,14 +41,14 @@ $$ 1. $A\subseteq B\Leftrightarrow x\le a\wedge b\le y$. 2. $A=B\Leftrightarrow a=x\wedge b=y$. 3. $A\cap B=(P,[\max(a,x),\min(b,y)])$. 4. $A\cup B=(P,[min(a,x),\max(b,y)])$. 4. $A\cup B=(P,[\min(a,x),\max(b,y)])$. 5. $A\setminus B=(P,\{i|i\in[a,b]\wedge i\notin[x,y]\})$. 其实这些运算就是普通的集合交并差放在区间上而已。 ### 连续段的性质 连续段的一些显而易见的性质。我们定义 $A,B\in I_P$,那么有 $A\cup B,A\cap B,A\setminus B,B\setminus A\in I_P$. 连续段的一些显而易见的性质。我们定义 $A,B\in I_P$,那么有 $A\cup B,A\cap B,A\setminus B,B\setminus A\in I_P$。 证明?证明的本质就是集合的交并差的运算。 Loading @@ -62,9 +60,9 @@ $$ 其实这个定义全称叫作**本原连续段**。但笔者认为本原段更为简洁。 对于排列 P,我们认为一个本原段 $M$ 表示在集合 $I_P$ 中,不存在与之相交且不包含的连续段。形式化地定义,我们认为 $X\in I_P$ 且满足 $\forall A\in I_P,\ X\cap A= (P,\varnothing)\vee X\subseteq A\vee A\subseteq X$. 对于排列 P,我们认为一个本原段 $M$ 表示在集合 $I_P$ 中,不存在与之相交且不包含的连续段。形式化地定义,我们认为 $X\in I_P$ 且满足 $\forall A\in I_P,\ X\cap A= (P,\varnothing)\vee X\subseteq A\vee A\subseteq X$。 所有本原段的集合为 $M_P$. 显而易见,$(P,\varnothing)\in M_P$. 所有本原段的集合为 $M_P$. 显而易见,$(P,\varnothing)\in M_P$。 显然,本原段之间只有相离或者包含关系。并且你发现**一个连续段可以由几个互不相交的本原段构成**。最大的本原段就是整个排列本身,它包含了其他所有本原段,因此我们认为本原段可以构成一个树形结构,我们称这个结构为**析合树**。更严格地说,排列 P 的析合树由排列 P 的**所有本原段**组成。 Loading @@ -80,7 +78,7 @@ $$ 1. **值域区间**:对于一个结点 u,用 $[u_l,u_r]$ 表示该结点的值域区间。 2. **儿子序列**:对于析合树上的一个结点 u,假设它的儿子结点是一个**有序**序列,该序列是以值域区间为元素的(单个的数 x 可以理解为 $[x,x]$ 的区间)。我们把这个序列称为儿子序列。记作 $S_u$。 3. **儿子排列**:对于一个儿子序列 $S_u$,把它的元素离散化成正整数后形成的排列称为儿子排列。举个例子,对于结点 $[5,8]$,它的儿子序列为 $\{[5,5],[6,7],[8,8]\}$,那么把区间排序标个号,则它的儿子排列就为 $\{1,2,3\}$;类似的,结点 $[4,8]$ 的儿子排列为 $\{2,1\}$。结点 u 的儿子排列记为 $P_u$. 3. **儿子排列**:对于一个儿子序列 $S_u$,把它的元素离散化成正整数后形成的排列称为儿子排列。举个例子,对于结点 $[5,8]$,它的儿子序列为 $\{[5,5],[6,7],[8,8]\}$,那么把区间排序标个号,则它的儿子排列就为 $\{1,2,3\}$;类似的,结点 $[4,8]$ 的儿子排列为 $\{2,1\}$。结点 u 的儿子排列记为 $P_u$。 4. **合点**:我们认为,儿子排列为顺序或者逆序的点为合点。形式化地说,满足 $P_u=\{1,2,\cdots,|S_u|\}$ 或者 $P_u=\{|S_u|,|S_u-1|,\cdots,1\}$ 的点称为合点。**叶子结点没有儿子排列,我们也认为它是合点**。 5. **析点**:不是合点的就是析点。 Loading @@ -88,7 +86,7 @@ $$ ### 析点与合点的性质 析点与合点的命名来源于他们的性质。首先我们有一个非常显然的性质:对于析合树中任何的结点 u,其儿子序列区间的并集就是结点 u 的值域区间。即 $\bigcup_{i=1}^{|S_u|}S_u[i]=[u_l,u_r]$. 析点与合点的命名来源于他们的性质。首先我们有一个非常显然的性质:对于析合树中任何的结点 u,其儿子序列区间的并集就是结点 u 的值域区间。即 $\bigcup_{i=1}^{|S_u|}S_u[i]=[u_l,u_r]$。 对于一个合点 u:其儿子序列的任意**子区间**都构成一个**连续段**。形式化地说,对于 $S_u[l\sim r]$,有 $\bigcup_{i=l}^rS_u[i]\in I_P$。 Loading @@ -106,7 +104,7 @@ $$ #### 增量法 我们考虑增量法。用一个栈维护前 i-1 个元素构成的析合森林。在这里我需要**着重强调**,析合森林的意思是,在任何时侯,栈中结点要么是析点要么是合点。现在考虑当前结点 $P_i$。 我们考虑增量法。用一个栈维护前 $i-1$ 个元素构成的析合森林。在这里我需要**着重强调**,析合森林的意思是,在任何时侯,栈中结点要么是析点要么是合点。现在考虑当前结点 $P_i$。 1. 我们先判断它能否成为栈顶结点的儿子,如果能就变成栈顶的儿子,然后把栈顶取出,作为当前结点。重复上述过程直到栈空或者不能成为栈顶结点的儿子。 2. 如果不能成为栈顶的儿子,就看能不能把栈顶的若干个连续的结点都合并成一个结点(判断能否合并的方法在后面),把合并后的点,作为当前结点。 Loading @@ -121,8 +119,8 @@ $$ 如果无法成为栈顶结点的儿子,那么我们就看栈顶连续的若干个点能否与当前点一起合并。我们预处理一个数组 $L$,$L_i$ 表示右端点下标为 $i$ 的连续段中,左端点的最小值。当前结点为 $P_i$ ,栈顶结点记为 $t$。 1. 如果 $t_l<L_i$ 那么显然当前结点无法合并; 2. 如果 $t_l=L$,那么这就是两个结点合并,合并后就是一个**合点**。 3. 如果在栈中存在一个点 $t'$ 的左端点 ${t'}_l=L_i$,那么一定可以从当前结点合并到 $t’$ 形成一个**析点**。 2. 如果 $t_l=L$,那么这就是两个结点合并,合并后就是一个**合点**; 3. 如果在栈中存在一个点 $t'$ 的左端点 ${t'}_l=L_i$,那么一定可以从当前结点合并到 $t’$ 形成一个**析点**; 4. 否则,我们找到栈中的一个点 $t'$ 使得 ${t'}_l<L_i\le {t'}_r$。由连续段的差运算可知 $(P,[{t'}_r+1,i])$ 也是连续段,于是合并 $t'$ 之后的结点到当前结点成一个**析点**即可。 #### 判断能否合并 Loading Loading @@ -170,7 +168,7 @@ $$ ### 实现 最后放一个实现的代码供参考。代码转自[大米饼的博客](https://www.cnblogs.com/Paul-Guderian/p/11020708.html). 被我加了一些注释 最后放一个实现的代码供参考。代码转自[大米饼的博客](https://www.cnblogs.com/Paul-Guderian/p/11020708.html),被我加了一些注释。 ```cpp #include<bits/stdc++.h> Loading Loading
docs/ds/divide-combine.md +15 −17 Original line number Diff line number Diff line 解释一下本文可能用到的符号:$\wedge$ 逻辑与,$\vee$ 逻辑或。 ## 关于段的问题 我们由一个小清新的问题引入: > 对于一个 1-n 的排列,我们称一个值域连续的区间为段。问一个排列的段的个数。比如,$\{5 ,3 ,4, 1 ,2\}$ 的段有:[1,1],[2,2],[3,3],[4,4],[5,5],[2,3],[4,5],[1,3],[2,5],[1,5]。 > 对于一个 1-n 的排列,我们称一个值域连续的区间为段。问一个排列的段的个数。比如,$\{5 ,3 ,4, 1 ,2\}$ 的段有:$[1,1],[2,2],[3,3],[4,4],[5,5],[2,3],[4,5],[1,3],[2,5],[1,5]$。 看到这个东西,感觉要维护区间的值域集合,复杂度好像挺不友好的。线段树可以查询某个区间是否为段,但不太能统计段的个数(也可能是因为我太菜了不会用线段树) Loading @@ -30,9 +28,9 @@ $$ (\nexists\ x,z\in[l,r],y\notin[l,r],\ P_x<P_y<P_z) $$ 特别地,当 $l>r$ 时,我们认为这时一个空的连续段,记作 $(P,\varnothing)$。 特别地,当 $l>r$ 时,我们认为这是一个空的连续段,记作 $(P,\varnothing)$。 我们称排列 P 的所有连续段的集合为 $I_P$. 并且我们认为 $(P,\varnothing)\in I_P$. 我们称排列 P 的所有连续段的集合为 $I_P$,并且我们认为 $(P,\varnothing)\in I_P$。 ### 连续段的运算 Loading @@ -43,14 +41,14 @@ $$ 1. $A\subseteq B\Leftrightarrow x\le a\wedge b\le y$. 2. $A=B\Leftrightarrow a=x\wedge b=y$. 3. $A\cap B=(P,[\max(a,x),\min(b,y)])$. 4. $A\cup B=(P,[min(a,x),\max(b,y)])$. 4. $A\cup B=(P,[\min(a,x),\max(b,y)])$. 5. $A\setminus B=(P,\{i|i\in[a,b]\wedge i\notin[x,y]\})$. 其实这些运算就是普通的集合交并差放在区间上而已。 ### 连续段的性质 连续段的一些显而易见的性质。我们定义 $A,B\in I_P$,那么有 $A\cup B,A\cap B,A\setminus B,B\setminus A\in I_P$. 连续段的一些显而易见的性质。我们定义 $A,B\in I_P$,那么有 $A\cup B,A\cap B,A\setminus B,B\setminus A\in I_P$。 证明?证明的本质就是集合的交并差的运算。 Loading @@ -62,9 +60,9 @@ $$ 其实这个定义全称叫作**本原连续段**。但笔者认为本原段更为简洁。 对于排列 P,我们认为一个本原段 $M$ 表示在集合 $I_P$ 中,不存在与之相交且不包含的连续段。形式化地定义,我们认为 $X\in I_P$ 且满足 $\forall A\in I_P,\ X\cap A= (P,\varnothing)\vee X\subseteq A\vee A\subseteq X$. 对于排列 P,我们认为一个本原段 $M$ 表示在集合 $I_P$ 中,不存在与之相交且不包含的连续段。形式化地定义,我们认为 $X\in I_P$ 且满足 $\forall A\in I_P,\ X\cap A= (P,\varnothing)\vee X\subseteq A\vee A\subseteq X$。 所有本原段的集合为 $M_P$. 显而易见,$(P,\varnothing)\in M_P$. 所有本原段的集合为 $M_P$. 显而易见,$(P,\varnothing)\in M_P$。 显然,本原段之间只有相离或者包含关系。并且你发现**一个连续段可以由几个互不相交的本原段构成**。最大的本原段就是整个排列本身,它包含了其他所有本原段,因此我们认为本原段可以构成一个树形结构,我们称这个结构为**析合树**。更严格地说,排列 P 的析合树由排列 P 的**所有本原段**组成。 Loading @@ -80,7 +78,7 @@ $$ 1. **值域区间**:对于一个结点 u,用 $[u_l,u_r]$ 表示该结点的值域区间。 2. **儿子序列**:对于析合树上的一个结点 u,假设它的儿子结点是一个**有序**序列,该序列是以值域区间为元素的(单个的数 x 可以理解为 $[x,x]$ 的区间)。我们把这个序列称为儿子序列。记作 $S_u$。 3. **儿子排列**:对于一个儿子序列 $S_u$,把它的元素离散化成正整数后形成的排列称为儿子排列。举个例子,对于结点 $[5,8]$,它的儿子序列为 $\{[5,5],[6,7],[8,8]\}$,那么把区间排序标个号,则它的儿子排列就为 $\{1,2,3\}$;类似的,结点 $[4,8]$ 的儿子排列为 $\{2,1\}$。结点 u 的儿子排列记为 $P_u$. 3. **儿子排列**:对于一个儿子序列 $S_u$,把它的元素离散化成正整数后形成的排列称为儿子排列。举个例子,对于结点 $[5,8]$,它的儿子序列为 $\{[5,5],[6,7],[8,8]\}$,那么把区间排序标个号,则它的儿子排列就为 $\{1,2,3\}$;类似的,结点 $[4,8]$ 的儿子排列为 $\{2,1\}$。结点 u 的儿子排列记为 $P_u$。 4. **合点**:我们认为,儿子排列为顺序或者逆序的点为合点。形式化地说,满足 $P_u=\{1,2,\cdots,|S_u|\}$ 或者 $P_u=\{|S_u|,|S_u-1|,\cdots,1\}$ 的点称为合点。**叶子结点没有儿子排列,我们也认为它是合点**。 5. **析点**:不是合点的就是析点。 Loading @@ -88,7 +86,7 @@ $$ ### 析点与合点的性质 析点与合点的命名来源于他们的性质。首先我们有一个非常显然的性质:对于析合树中任何的结点 u,其儿子序列区间的并集就是结点 u 的值域区间。即 $\bigcup_{i=1}^{|S_u|}S_u[i]=[u_l,u_r]$. 析点与合点的命名来源于他们的性质。首先我们有一个非常显然的性质:对于析合树中任何的结点 u,其儿子序列区间的并集就是结点 u 的值域区间。即 $\bigcup_{i=1}^{|S_u|}S_u[i]=[u_l,u_r]$。 对于一个合点 u:其儿子序列的任意**子区间**都构成一个**连续段**。形式化地说,对于 $S_u[l\sim r]$,有 $\bigcup_{i=l}^rS_u[i]\in I_P$。 Loading @@ -106,7 +104,7 @@ $$ #### 增量法 我们考虑增量法。用一个栈维护前 i-1 个元素构成的析合森林。在这里我需要**着重强调**,析合森林的意思是,在任何时侯,栈中结点要么是析点要么是合点。现在考虑当前结点 $P_i$。 我们考虑增量法。用一个栈维护前 $i-1$ 个元素构成的析合森林。在这里我需要**着重强调**,析合森林的意思是,在任何时侯,栈中结点要么是析点要么是合点。现在考虑当前结点 $P_i$。 1. 我们先判断它能否成为栈顶结点的儿子,如果能就变成栈顶的儿子,然后把栈顶取出,作为当前结点。重复上述过程直到栈空或者不能成为栈顶结点的儿子。 2. 如果不能成为栈顶的儿子,就看能不能把栈顶的若干个连续的结点都合并成一个结点(判断能否合并的方法在后面),把合并后的点,作为当前结点。 Loading @@ -121,8 +119,8 @@ $$ 如果无法成为栈顶结点的儿子,那么我们就看栈顶连续的若干个点能否与当前点一起合并。我们预处理一个数组 $L$,$L_i$ 表示右端点下标为 $i$ 的连续段中,左端点的最小值。当前结点为 $P_i$ ,栈顶结点记为 $t$。 1. 如果 $t_l<L_i$ 那么显然当前结点无法合并; 2. 如果 $t_l=L$,那么这就是两个结点合并,合并后就是一个**合点**。 3. 如果在栈中存在一个点 $t'$ 的左端点 ${t'}_l=L_i$,那么一定可以从当前结点合并到 $t’$ 形成一个**析点**。 2. 如果 $t_l=L$,那么这就是两个结点合并,合并后就是一个**合点**; 3. 如果在栈中存在一个点 $t'$ 的左端点 ${t'}_l=L_i$,那么一定可以从当前结点合并到 $t’$ 形成一个**析点**; 4. 否则,我们找到栈中的一个点 $t'$ 使得 ${t'}_l<L_i\le {t'}_r$。由连续段的差运算可知 $(P,[{t'}_r+1,i])$ 也是连续段,于是合并 $t'$ 之后的结点到当前结点成一个**析点**即可。 #### 判断能否合并 Loading Loading @@ -170,7 +168,7 @@ $$ ### 实现 最后放一个实现的代码供参考。代码转自[大米饼的博客](https://www.cnblogs.com/Paul-Guderian/p/11020708.html). 被我加了一些注释 最后放一个实现的代码供参考。代码转自[大米饼的博客](https://www.cnblogs.com/Paul-Guderian/p/11020708.html),被我加了一些注释。 ```cpp #include<bits/stdc++.h> Loading