splay

Link_Cut_Tree

Deadly 提交于 2019-12-10 14:20:56
LCT就是动态树的一种,但是相比Top_Tree来说,处理子树能力不足(但是简单)。 LCT利用轻重路径的划分,也就是实边和虚边来实现边的删除和合并。 现在来说说LCT的几个重要操作:(默认会splay) access(x) : 让x连到根上面,也就是让根到x的边全部变为实边。所以我们每次让当前节点splay到根,改变儿子,向上pushup传递信息即可。 makeroot(x) : 让x为根,也就是换根操作,我们先access(x),之后x肯定为深度最大的点,然后splay到根即可。 find(x) : 找到原树的根,也是一样,我们先access(x) ,之后把根换到最底层,也就是把x,splay到顶点,然后一直向左儿子走即可走到原根。 split(x,y) :向然x为根,即makeroot(x),之后打通y到x的路径,access(y),然后把y,splay到根即可得到以y为根,x到y的路径。 link(x,y) :连接x,y两个点(变成实边),我们令x为根,如果y的根不为x就让x的父亲为y即可。 cut(x,y) :切断x,y两点之间的边,先让x为根,如果满足y的父亲,y的根都为x且y没有左儿子,切断即可。 例题: Link_Cut_Tree例题 AC代码: # pragma GCC optimize(2) # include <bits/stdc++.h> //#define

Splay与FHQ-Treap

我与影子孤独终老i 提交于 2019-12-09 11:23:15
两个一起学的,就放一块了。 主要是用来存板子。 Splay //This is a Splay Tree. #include <cstdio> #include <cstring> using namespace std; const int N=1e5+5,INF=0x3f3f3f3f; int n,root,cntnode; struct node //始终满足左小右大 { int fa,ch[2],val,siz,cnt; //int mark; //区间反转标记 }t[N]; inline bool get(int x) {return t[t[x].fa].ch[1]==x;} //右儿子?1:0 inline void upd(int x) {t[x].siz=t[t[x].ch[0]].siz+t[t[x].ch[1]].siz+t[x].cnt;} //更新计数 inline void zigzag(int x) //旋转操作 { int fa=t[x].fa; int gfa=t[fa].fa; int d1=get(x),d2=get(fa); t[fa].ch[d1]=t[x].ch[d1^1]; t[t[x].ch[d1^1]].fa=fa; //断开fa与x,连接fa与x的儿子 t[gfa].ch[d2]=x; t[x].fa=gfa; /

LCT 讲解 动态树的基本使用

社会主义新天地 提交于 2019-12-08 04:19:15
Link-Cut-tree 动态树解决树上问题的一种数据结构,没学过树链剖分的建议先学一下树链剖分。 你们先假装会了树链剖分 QwQ。 树链剖分是对树进行轻重链剖分,重链的条数不超过logn条,用线段树维护链上信息。 树链剖分可支持的操作有: 链上求和 链上求最值 链上修改 但是对于 断开树上的一条边 或 连接两个点,保证连接后仍然是一棵树 由于树是动态的,重建需要重新标号,复杂度略高~ 所以就有了动态树这种东西,我们把静态的线段树替换成Splay 于是就有了 LCT=树链剖分+Splay 这里引用一下网上的一些概念 “Preferred Child:重儿子(为了便于理解这里沿用树链剖分中的命名),重儿子与父亲节点同在一棵Splay中,一个节点最多只能有一个重儿子 Preferred Edge:重边,连接父亲节点和重儿子的边 Preferred Path:重链,由重边及重边连接的节点构成的链 ” 有点像树链剖分。 由一条链上的所有的点组成的Splay称作这条链的辅助树,键值为节点深度,所以整个Splay的中序遍历就是整个序列。 辅助树的根节点指向链顶的父亲,注意辅助树的根节点≠原树的根节点。 由于需要维护的信息在辅助树里存在,所以只需维护辅助树就行了 代码实现(解释) inline bool isroot( int x) { return tree[fa[x]][ 0 ]!=x&

【模板】LCT

点点圈 提交于 2019-12-07 13:26:29
LCT: 动态维护一个森林。支持删边,加边,查询链信息等很多操作。 由若干棵$Splay$组成,每棵$Splay$维护一条链,以深度作为关键字。 也就是说$Splay$的中序遍历相当于从上到下遍历这条链。 $Splay$中的边是实边,将两个$Splay$相连的边是虚边。 实边的父亲有它这个儿子(双向关系),虚边的父亲没有它这个儿子(单向关系)。 组成$LCT$的基础操作: (以下均认为$LCT$中只有一棵树) $access(x)$:打通根到$x$的路径,使一棵包含且仅包含根到$x$这条链上点的$Splay$出现。 实现方法: 1.$splay(x)$:将$x$转到当前$Splay$的根。(此时$x$是该$Splay$中最深的点,没有右儿子) 2.$c[x][1]=y$:将$x$的右儿子设为刚才操作的$Splay$的根。 3.$x=f[x]$:继续操作$x$在原树中的父亲,若$x$已经为根则退出。 $makeroot(x)$:使$x$成为根。 实现方法: 1.$access(x)$。 2.$splay(x)$。 3.翻转整棵$Splay$。 为什么不能只翻转$x$的左右儿子:一个链提末端点当根之后整个链的深度顺序全部翻转。 如:$1-2-3$翻转后为$3-2-1$而不是$3-1-2$。 $findroot(x)$:找$x$所在原树的根。 实现方法: 1.$access(x)$。 2

LCT学习笔记

安稳与你 提交于 2019-12-07 09:12:29
  这里是学了两遍LCT,又忘了两遍的shzr。   今天让我们再来学一遍LCT;这次我写了笔记,以后忘了就看看笔记,不用花那么多时间了。   首先来稍微复习一下LCT的一些基本知识:   - LCT用来维护森林;   - 一棵LCT由多棵Splay组成,每棵Splay里的点是树上的一段路径。具体来说,是树上一段"祖先-子孙"路径。同时,每个树上的点会且只会出现在一个Splay中;    - 中序遍历每个Splay,得到的点的深度序列是递增的;    - 森林中的边被分为两类:虚边和实边;这两类边在LCT中不一定真的出现,但是都会有体现:对于按照Splay中序遍历得到的点序列,相邻两点间的边就是实边;每个Splay还有一个总的"祖先",从这个Splay中深度最小的点到这个"祖先"之间存在一条虚边;不要错误的以为成Splay的根到那个"祖先"的边,因为Splay的根是随意的;   接着来学习各种基本操作: $\rm Access(x)$   值得注意的是,虚实边的划分并没有什么原理,其实是可以随便划分的;   $assess(x)$ 这个操作就是指:将森林中 $x$ 到树根的这条链全部变实,同时 $x$ 变为这条实链中深度最大的点(不再有实儿子);   其实方法是很简单的...首先将 $x$ 旋转到所在Splay的根,然后断掉 $x$ 的右儿子,连上上一次循环时的 $x$(因为 $x

我想要打一个Splay

萝らか妹 提交于 2019-12-07 01:38:26
大规模单步调试现场 这里只提供模板,详细解说请参见 这位大佬 的题解。 注意,这位大佬的rank操作跑的飞飞飞飞飞慢,利用Find操作优化之后可以极大优化时间 #include<cstdio> #include<iostream> #include<cmath> #include<fstream> #include<time.h> using namespace std; char *p1,*p2,buf[1<<20]; #define GC (p1==p2&&(p1=buf,p2=buf+fread(buf,1,1<<20,stdin),p1==p2)?0:(*(p1++))) //#define GC getchar() inline int in() { int x=0,w=0; char ch=0; while(!isdigit(ch)){ w|=ch=='-'; ch=GC; } while(isdigit(ch)){ x=(x<<3)+(x<<1)+(ch^48); ch=GC; } return w?-x:x; } struct node{ int id,f; int son[2]; int size; int cnt; }; const int maxn=200010; struct tree{ node t[maxn]; int n,points; #define

动态树(LCT、Top Tree、ETT)

▼魔方 西西 提交于 2019-12-06 10:55:12
LCT LCT是一种维护森林的数据结构,本质是用Splay维护实链剖分。 实链剖分大概是这样的:每个节点往一个儿子连实边,其它的儿子连虚边。 而我们用Splay维护实链剖分后的每一条实链。 因此LCT有一些基本的性质: \(1.\) 每一棵Splay维护树上一条直上直下的实链,且其中序遍历的点的序列的深度递增。 \(2.\) 每个节点包含且仅包含于一棵Splay。 \(3.\) 实边包含于Splay中,而虚边则连接两棵Splay。对于每个节点而言,它会记录它在Splay中的父亲,而play的根节点的父亲则是原树中这棵Splay代表的链的链顶的父亲。但是每个节点只会记录它在Splay中的左右儿子,并不会记录由虚边连接的儿子。( 认父不认子 ) 下面如果没有特殊说明,我们默认我们所说的树为Splay而非原树。 然后我们先来说几个预备的基本操作: nrooot nroot实现判断一个节点是否为该节点所在Splay的根节点。 根据认父不认子的特性,我们只需要判断该节点的父亲是否有它这个儿子即可。 int nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;} pushrev pushrev实现把一个子树翻转。 在后面的操作需要用到。 具体为交换左右儿子,并给左右儿子打上翻转标记。 void pushrev(int x){swap(lc

平衡树Splay

回眸只為那壹抹淺笑 提交于 2019-12-04 04:12:48
LuoguP3369 这道题啊。。。 一开始学习的时候遇到了一篇讲的超级好的博客: rentenglong 的博客 这篇大家一定要去瞅瞅,讲的炒鸡详细(就是代码有点锅。。。) 后来找到一份和TA码风差不多的,才完善了一哈qwq 不过最终还是过了 Code: #include <bits/stdc++.h> #define root e[0].son[1] using namespace std; int read() { int re = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') {if (ch == '-') f = -f; ch = getchar();} while ('0' <= ch && ch <= '9') {re = re * 10 + ch - '0'; ch = getchar();} return re * f; } const int N = 1e5 + 3; const int INF = 1e7 + 3; int n, cnt; struct node{ int v, father;//储存键值,父亲节点 int son[2];//存储左右孩子,son[0]为左,son[1]为右 int sum;//存储这个节点子树共有多少 元素 (元素包括sum + recy) int

技巧

北战南征 提交于 2019-12-04 04:01:19
生成匹配的括号序列,只需模拟一个栈,记录栈中的括号数即可(CF1015F,CF508E)。 AC自动机算法在匹配时,在fail树上的链将都被匹配,所以经常与树上算法联合使用(luogu P3796,luogu P2336喵星球上的点名)。 平面图的最小割就是对偶图的最短路(每个面看做一个点,相邻的面看做边)(luogu P2046海拔,luogu 狼抓兔子)。 求第k小值(k不大),可以采用A*算法,要保证每种状态都能被扩展出,且扩展出的状态非递减(luogu 超级钢琴,K短路,OVOO)。 互质数的选择,当质因数较大时采用分组背包,较小时状压记录,以平方根为界(luogu寿司晚宴)。 有时,权值线段树可以代替treap,能减小常数和代码量(luogu 郁闷的出纳员)。 有时,线段树可以代替splay,方法时记录每个位置是否有元素,查询第k个值时线段树上二分(NOIP2017 列队)。 在一棵树上统计子树信息,如果子树信息不能快速合并,可以采用dsu on tree算法(luogu 雨天的尾巴,回文路径计数)。 对于状态转移有环的动态规划,使用如下方式求解: 若转移里没有max/min,只有加减乘除运算,可以建立方程组,通过高斯消元在O(n^3)得出解。 若转移只是对一些元素取max/min(或第k大/小值),再加/减一个值,可以建图,使用dij或spfa求解。 若u依赖v

列队「NOIP2017」

元气小坏坏 提交于 2019-12-04 02:31:30
题意 懒得粘贴了,自己去找吧。 思路 这个splay调一年,自闭了。 思路就是每一行建一个splay,然后再建一个维护行尾。 每次操作就分割区间,每个splay最开始都是完整区间。 代码 没有,自己去找 \(\color{black}r\color{red}{qy}\) 的代码。 这个splay实在搞人心态,我太菜了。 来源: https://www.cnblogs.com/ilverene/p/11828586.html