Loading docs/ds/lct.md +22 −22 Original line number Diff line number Diff line Loading @@ -107,6 +107,7 @@ - `tag[N]` 翻转标记 - `laz[N]` 权值标记 - `siz[N]` 辅助树上子树大小 - Other_Vars ### 函数声明 Loading @@ -132,7 +133,7 @@ 6. Cut(x, y) 把 x, y 两点间边删掉。 7. Find(x) 找到 x 所在的 Splay 的根节点编号。 8. Fix(x, v) 修改 x 的点权为 v。 9. Split(x, y) 提取出来 x, y 间的路路径,方便做区间操作。 9. Split(x, y) 提取出 x, y 间的路径,方便做区间操作。 ### 宏定义 Loading @@ -147,8 +148,7 @@ ```cpp inline void PushUp(int p) { __var1[p] = __var1[ls] "operator 1" __var1[rs] "operator 2" __var1[p] / __siz[p]; // maintain other variables siz[p] = siz[ls] + siz[rs]; } ``` Loading @@ -157,9 +157,9 @@ inline void PushUp(int p) { ```cpp inline void PushDown(int p) { if (__tag1[p] != std_tag1) { // do ls & do rs __tag1[p] = std_tag1; if (tag[p] != std_tag) { // pushdown the tag tag[p] = std_tag; } } ``` Loading Loading @@ -203,7 +203,7 @@ inline void Splay(int x) { ```cpp // Access 是 LCT // 的核心操作,试想我们像求解一条路径,而这条路径恰好就是我们当前的一棵 Splay, // 直接调用其信息即可 先来看一下代码,再结合图来看看过程 // 直接调用其信息即可。先来看一下代码,再结合图来看看过程 inline void Access(int x) { for (int p = 0; x; p = x, x = f[x]) { Splay(x), ch[x][1] = p, PushUp(x); Loading @@ -220,15 +220,15 @@ inline void Access(int x) {  - 现在我们要 Access(N), 把 A-N 的路径都变实,拉成一棵 Splay - 现在我们要 Access(N), 把 A 到 N 路径上的边都变为实边,拉成一棵 Splay  - 实现的方法是从下到上逐步更新 Splay - 首先我们要把 N 旋至当前 Splay 的根。 - 为了保证 AuxTree 的性质,原来 N——O 的实边要更改为虚边。 - 为了保证 AuxTree(辅助树)的性质,原来 N 到 O 的实边要更改为虚边。 - 由于认父不认子的性质,我们可以单方面的把 N 的儿子改为 Null。 - 于是原来的 Aux 就从下图变成了下下图。 - 于是原来的 AuxTree 就从下图变成了下下图。  Loading @@ -236,7 +236,7 @@ inline void Access(int x) { - 下一步,我们把 N 指向的 Father-> I 也旋转到它 (I) 的 Splay 树根。 - 原来的实边 I——K 要去掉,这时候我们把 I 的右儿子指向 N, 就得到了 I——L 这样一棵 Splay。 - 原来的实边 I—K 要去掉,这时候我们把 I 的右儿子指向 N, 就得到了 I—L 这样一棵 Splay。  Loading Loading @@ -279,11 +279,11 @@ void Update(int p) { ### `makeRoot()` - Make_Root() 的重要性丝毫不亚于 Access()。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 Aux 的性质,这种路径是不能出现在一棵 Splay 中的。 - Make_Root() 的重要性丝毫不亚于 Access()。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 AuxTree 的性质,这种路径是不能出现在一棵 Splay 中的。 - 这时候我们需要用到 Make_Root()。 - Make_Root()。的作用是使指定的点成为原树的根,考虑如何实现这种操作。 - Make_Root() 的作用是使指定的点成为原树的根,考虑如何实现这种操作。 - 我们发现 Access(x) 后,x 在 Splay 中一定是深度最大的点(从根到 x, 深度严格递增)。 - 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树(即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,在 Aux 意义上就是深度最小的点了,即达到目的。 - 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树(即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,在 AuxTree 意义上就是深度最小的点了,即达到目的。 - 所以我们交换左右儿子,并给右儿子打一个翻转标记即可。(此时左儿子没有值)。 ```cpp Loading @@ -296,7 +296,7 @@ inline void makeRoot(int p) { ### `Link()` - Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内 OTZ。记得先判一下。 - Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内,所以记得先判一下。 ```cpp inline void Link(int x, int p) { Loading @@ -308,14 +308,14 @@ inline void Link(int x, int p) { ### `Split()` - Split 操作意义很简单,就是拿出一棵 Splay , 维护的是 x 到 y 的路径。 - 先 MakeRoot(x) , 然后 Access(y)。如果要 y 做根,再 Splay(y)。 - 先 MakeRoot(x),然后 Access(y)。如果要 y 做根,再 Splay(y)。 - 就这三句话,没写代码,需要的时候可以直接打这三个就好辣! - 另外 Split 这三个操作直接可以把需要的路径拿出到 y 的子树上,那不是随便干嘛咯。 ### `Cut()` - Cut 有两种情况,保证合法和不一定保证合法。(废话) - 如果保证合法,直接 split(x, y) , 这时候 y 是根,x 一定是它的儿子,双向断开即可 , 就像这样: - 如果保证合法,直接 split(x, y),这时候 y 是根,x 一定是它的儿子,双向断开即可。就像这样: ```cpp inline void Cut(int x, int p) { Loading @@ -323,14 +323,14 @@ inline void Cut(int x, int p) { } ``` 如果是不保证合法,我们需要判断一下是否有,我选择使用 Map 存一下,但是这里有一个利用性质的方法: 如果是不保证合法,我们需要判断一下是否有,我选择使用 map 存一下,但是这里有一个利用性质的方法: 想要删边,必须要满足如下三个条件: 1. x, y 连通。 2. x, y 的路径上没有其他的链。 3. x 没有右儿子。 4. 总结一下,上面三句话的意思就一个:x, y 有边。 4. 总结一下,上面三句话的意思就一个:x, y 之间有边。 具体实现就留作一个思考题给大家。判断连通需要用到后面的 Find , 其他两点稍作思考分析一下结构就知道该怎么判断了。 Loading @@ -355,7 +355,7 @@ inline int Find(int p) { ## 一些题 - [「BZOJ 2049」[SDOI2008] Cave 洞穴勘测](https://lydsy.com/JudgeOnline/problem.php?id=2049) - [「SDOI2008」 Cave 洞穴勘测](https://lydsy.com/JudgeOnline/problem.php?id=2049) - [「BZOJ 3282」Tree](https://lydsy.com/JudgeOnline/problem.php?id=3282) - [「BZOJ 2002」[HNOI2010] Bounce 弹飞绵羊](https://lydsy.com/JudgeOnline/problem.php?id=2002) - [「HNOI2010」 Bounce 弹飞绵羊](https://lydsy.com/JudgeOnline/problem.php?id=2002) - [「BZOJ 2631」tree](https://lydsy.com/JudgeOnline/problem.php?id=2631) Loading
docs/ds/lct.md +22 −22 Original line number Diff line number Diff line Loading @@ -107,6 +107,7 @@ - `tag[N]` 翻转标记 - `laz[N]` 权值标记 - `siz[N]` 辅助树上子树大小 - Other_Vars ### 函数声明 Loading @@ -132,7 +133,7 @@ 6. Cut(x, y) 把 x, y 两点间边删掉。 7. Find(x) 找到 x 所在的 Splay 的根节点编号。 8. Fix(x, v) 修改 x 的点权为 v。 9. Split(x, y) 提取出来 x, y 间的路路径,方便做区间操作。 9. Split(x, y) 提取出 x, y 间的路径,方便做区间操作。 ### 宏定义 Loading @@ -147,8 +148,7 @@ ```cpp inline void PushUp(int p) { __var1[p] = __var1[ls] "operator 1" __var1[rs] "operator 2" __var1[p] / __siz[p]; // maintain other variables siz[p] = siz[ls] + siz[rs]; } ``` Loading @@ -157,9 +157,9 @@ inline void PushUp(int p) { ```cpp inline void PushDown(int p) { if (__tag1[p] != std_tag1) { // do ls & do rs __tag1[p] = std_tag1; if (tag[p] != std_tag) { // pushdown the tag tag[p] = std_tag; } } ``` Loading Loading @@ -203,7 +203,7 @@ inline void Splay(int x) { ```cpp // Access 是 LCT // 的核心操作,试想我们像求解一条路径,而这条路径恰好就是我们当前的一棵 Splay, // 直接调用其信息即可 先来看一下代码,再结合图来看看过程 // 直接调用其信息即可。先来看一下代码,再结合图来看看过程 inline void Access(int x) { for (int p = 0; x; p = x, x = f[x]) { Splay(x), ch[x][1] = p, PushUp(x); Loading @@ -220,15 +220,15 @@ inline void Access(int x) {  - 现在我们要 Access(N), 把 A-N 的路径都变实,拉成一棵 Splay - 现在我们要 Access(N), 把 A 到 N 路径上的边都变为实边,拉成一棵 Splay  - 实现的方法是从下到上逐步更新 Splay - 首先我们要把 N 旋至当前 Splay 的根。 - 为了保证 AuxTree 的性质,原来 N——O 的实边要更改为虚边。 - 为了保证 AuxTree(辅助树)的性质,原来 N 到 O 的实边要更改为虚边。 - 由于认父不认子的性质,我们可以单方面的把 N 的儿子改为 Null。 - 于是原来的 Aux 就从下图变成了下下图。 - 于是原来的 AuxTree 就从下图变成了下下图。  Loading @@ -236,7 +236,7 @@ inline void Access(int x) { - 下一步,我们把 N 指向的 Father-> I 也旋转到它 (I) 的 Splay 树根。 - 原来的实边 I——K 要去掉,这时候我们把 I 的右儿子指向 N, 就得到了 I——L 这样一棵 Splay。 - 原来的实边 I—K 要去掉,这时候我们把 I 的右儿子指向 N, 就得到了 I—L 这样一棵 Splay。  Loading Loading @@ -279,11 +279,11 @@ void Update(int p) { ### `makeRoot()` - Make_Root() 的重要性丝毫不亚于 Access()。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 Aux 的性质,这种路径是不能出现在一棵 Splay 中的。 - Make_Root() 的重要性丝毫不亚于 Access()。我们在需要维护路径信息的时候,一定会出现路径深度无法严格递增的情况,根据 AuxTree 的性质,这种路径是不能出现在一棵 Splay 中的。 - 这时候我们需要用到 Make_Root()。 - Make_Root()。的作用是使指定的点成为原树的根,考虑如何实现这种操作。 - Make_Root() 的作用是使指定的点成为原树的根,考虑如何实现这种操作。 - 我们发现 Access(x) 后,x 在 Splay 中一定是深度最大的点(从根到 x, 深度严格递增)。 - 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树(即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,在 Aux 意义上就是深度最小的点了,即达到目的。 - 而变成根即是变成深度最小的点。我们 Splay(x) , 发现这时候 x 并没有右子树(即所有点深度都比它浅)。那我们把 x 的左右儿子交换一下,变成了 x 没有左子树,在 AuxTree 意义上就是深度最小的点了,即达到目的。 - 所以我们交换左右儿子,并给右儿子打一个翻转标记即可。(此时左儿子没有值)。 ```cpp Loading @@ -296,7 +296,7 @@ inline void makeRoot(int p) { ### `Link()` - Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内 OTZ。记得先判一下。 - Link 两个点其实很简单,先 Make_Root(x) , 然后把 x 的父亲指向 y 即可。显然,这个操作肯定不能发生在同一棵树内,所以记得先判一下。 ```cpp inline void Link(int x, int p) { Loading @@ -308,14 +308,14 @@ inline void Link(int x, int p) { ### `Split()` - Split 操作意义很简单,就是拿出一棵 Splay , 维护的是 x 到 y 的路径。 - 先 MakeRoot(x) , 然后 Access(y)。如果要 y 做根,再 Splay(y)。 - 先 MakeRoot(x),然后 Access(y)。如果要 y 做根,再 Splay(y)。 - 就这三句话,没写代码,需要的时候可以直接打这三个就好辣! - 另外 Split 这三个操作直接可以把需要的路径拿出到 y 的子树上,那不是随便干嘛咯。 ### `Cut()` - Cut 有两种情况,保证合法和不一定保证合法。(废话) - 如果保证合法,直接 split(x, y) , 这时候 y 是根,x 一定是它的儿子,双向断开即可 , 就像这样: - 如果保证合法,直接 split(x, y),这时候 y 是根,x 一定是它的儿子,双向断开即可。就像这样: ```cpp inline void Cut(int x, int p) { Loading @@ -323,14 +323,14 @@ inline void Cut(int x, int p) { } ``` 如果是不保证合法,我们需要判断一下是否有,我选择使用 Map 存一下,但是这里有一个利用性质的方法: 如果是不保证合法,我们需要判断一下是否有,我选择使用 map 存一下,但是这里有一个利用性质的方法: 想要删边,必须要满足如下三个条件: 1. x, y 连通。 2. x, y 的路径上没有其他的链。 3. x 没有右儿子。 4. 总结一下,上面三句话的意思就一个:x, y 有边。 4. 总结一下,上面三句话的意思就一个:x, y 之间有边。 具体实现就留作一个思考题给大家。判断连通需要用到后面的 Find , 其他两点稍作思考分析一下结构就知道该怎么判断了。 Loading @@ -355,7 +355,7 @@ inline int Find(int p) { ## 一些题 - [「BZOJ 2049」[SDOI2008] Cave 洞穴勘测](https://lydsy.com/JudgeOnline/problem.php?id=2049) - [「SDOI2008」 Cave 洞穴勘测](https://lydsy.com/JudgeOnline/problem.php?id=2049) - [「BZOJ 3282」Tree](https://lydsy.com/JudgeOnline/problem.php?id=3282) - [「BZOJ 2002」[HNOI2010] Bounce 弹飞绵羊](https://lydsy.com/JudgeOnline/problem.php?id=2002) - [「HNOI2010」 Bounce 弹飞绵羊](https://lydsy.com/JudgeOnline/problem.php?id=2002) - [「BZOJ 2631」tree](https://lydsy.com/JudgeOnline/problem.php?id=2631)