Link Cat Tree

一、感性定义

所谓连喵树,即一种对森林支持修改,查询,连边,删边等操作的数据结构(姑且算她是吧)。她用一颗颗互相连接的辅助树维护原森林的信息,辅助树相互连接的边叫虚边,辅助树内相互连接的边叫实边

二、关于辅助树和原森林

1.辅助树的点代表的就是原森林的点,一般我们选取splay作为辅助树。

以原树中节点的深度作为二叉排序树的权值。也就是说,如果我们中序遍历splay,得到的节点深度是严格递增的。

附注:这里的“原树”指“原森林”中的一颗树,下同。

2.辅助树维护的是原树中一条链,因为我们已经保证维护了点的深度,所以点在splay中的形态是不定的,splay互相的形态也可能不同

比如对这个这个原树,它本来是这个样子的

我们用辅助树对它进行操作后,它可能是这个样子的(虚线是虚边,颜色相同为一颗splay)

也可能是这个样子的

还可能是这个样子的

总结一下:我们确保splay中的边是真实的边\(^①\)且保证深度满足要求,而splay互相连接的边不一定是真实的边,它只是代表这两个splay有连接

3.边的表示及存储

如果两个点的父子关系是相互的,那么这条边是实边,两个点在一个splay中

如果两个点的父子关系不是相互的,即一个点的父亲的儿子中确没有自己,代表这是一条虚边,表示splay之间有连接

我们发现,只有splay中的根节点可能存在一条虚边。并且如果这条根节点没有虚边,那么就代表这个点是原树的根。

4.换根

为了很好的查询信息,我们需要能够替换原树的根的操作(后面讲)

三、操作与实现

(1)★\(Access(x)\)★

作用:表示将\(x\)向原树根节点打通一条链,并将这条链搞到一颗splay里面

操作:暴力向上找父亲跳(当然跳的是splay),把右儿子置为跳过来的splay并更新信息

Code:

  1. void access(int now)//在辅助树中打通一条到原树的链
  2. {
  3. for(int las=0;now;las=now,now=fa)
  4. splay(now),rs=las,updata(now);
  5. }

这个操作是核心,请好好理解

(2)\(evert(x)\)

作用:将\(x\)置为原树的根节点

操作:首先我们把树打通一条链到根,然后\(splay\)到根。为了保证深度越小的点在中序遍历越靠前,我们要像文艺平衡树一样,把区间给翻转,带上标记

Code:

  1. void evert(int now)//将节点提至原树根节点
  2. {
  3. access(now),splay(now),Reverse(now);//打通,丢上去,翻转
  4. }

(3)\(findroot(x)\)

作用:找到\(x\)所在原树的根

操作:先打通一条链到根,然后splay上去,最左边的节点就是根了

Code:

  1. int findroot(int now)//寻找原树根节点
  2. {
  3. access(now),splay(now);
  4. while(ls) now=ls;
  5. return now;
  6. }

(4)\(split(x,y)\)

这个操作可以不写,为了方便我们才写它

作用:把\(x\)到\(y\)搞成一条路径放在一颗splay里并且\(y\)为splay的根

操作:把\(x\)放到它所在原树的根,然后把\(y\)给splay上去

我们可以不保证它们一定在一颗原树里,当做无效操作即可

Code:

  1. void split(int u,int v)//把链抽进辅助树(可以不在一颗树)
  2. {
  3. evert(u),access(v),splay(v);//放至根,打通,丢上去
  4. }

(5)\(link(x,y)\)

作用:连接节点\(x\)和节点\(y\)

操作:把\(x\)变成它所在树的根,然后只把\(x\)的父亲改成\(y\)(连虚边)

如果要不保证合法性,加个判断就行

Code:

  1. void link(int u,int v)//连边
  2. {
  3. evert(u);//搞到根
  4. if(findroot(v)!=u)//保证不在一颗树
  5. par[u]=v;//只连了虚边
  6. }

(6)\(cat(x,y)\)

作用:切断节点\(x\)与节点\(y\)之间的边

操作:把\(x\)和\(y\)搞到一颗splay里面,然后断双向边且更新答案。

如果不保证合法性,同样加个判断就行

Code:

  1. void cat(int u,int v)//断边
  2. {
  3. split(u,v);
  4. if(ch[v][0]==u)//直接相连
  5. par[u]=ch[v][0]=0,updata(v);//双向断边注意更新
  6. }

(7)其他

其他除了前两个操作,其他操作基本可以做的更小的常数,但是以上的比较好理解。

还有一些其他的询问操作或者修改操作就因题而异了。

四、与原先splay的不同之处

(1)\(isroot(x)\)

判断节点\(x\)是不是splay的根

Code:

  1. bool isroot(int now)//判断是否为子树的根
  2. {
  3. return ch[fa][0]==now||ch[fa][1]==now;
  4. }

(2)旋转时

旋转时不要连多了

因为大家旋转可能写的都不一样,所以就不放代码了

(3)splay时

我们得先找到到根的那条链,然后从上往下把翻转标记下发,然后再进行旋转

Code:

  1. void splay(int now)
  2. {
  3. int tot=0;
  4. while(isroot(now)) s[++tot]=now,now=fa;//先拿栈存储链
  5. s[++tot]=now;
  6. while(tot) pushdown(s[tot--]);//从上往下下放
  7. now=s[1];
  8. for(;isroot(now);Rotate(now))
  9. if(isroot(fa))
  10. Rotate(identity(now)^identity(fa)?now:fa);
  11. }

五、洛谷P3690 【模板】Link Cut Tree (动态树)代码参考

Code:

  1. #include <cstdio>
  2. #define ls ch[now][0]
  3. #define rs ch[now][1]
  4. #define fa par[now]
  5. const int N=3e5+10;
  6. int ch[N][2],dat[N],sum[N],tag[N],par[N],s[N],n,m,tmp;
  7. void updata(int now)//正常的更新
  8. {
  9. sum[now]=sum[ls]^sum[rs]^dat[now];
  10. }
  11. bool isroot(int now)//判断是否为子树的根
  12. {
  13. return ch[fa][0]==now||ch[fa][1]==now;
  14. }
  15. int identity(int now)//判断是哪个儿子
  16. {
  17. return ch[fa][1]==now;
  18. }
  19. void connect(int f,int now,int typ)//确定双向父子关系
  20. {
  21. fa=f;ch[f][typ]=now;
  22. }
  23. void Reverse(int now)//区间翻转,标记管儿子
  24. {
  25. tmp=ls,ls=rs,rs=tmp,tag[now]^=1;
  26. }
  27. void pushdown(int now)//下发标记
  28. {
  29. if(tag[now])
  30. {
  31. if(ls) Reverse(ls);
  32. if(rs) Reverse(rs);
  33. tag[now]^=1;
  34. }
  35. }
  36. void Rotate(int now)//旋转
  37. {
  38. int p=fa,typ=identity(now);
  39. connect(p,ch[now][typ^1],typ);
  40. if(isroot(p)) connect(par[p],now,identity(p));//注意判断父亲是否为辅助树的根节点
  41. else fa=par[p];
  42. connect(now,p,typ^1);
  43. updata(p),updata(now);
  44. }
  45. void splay(int now)
  46. {
  47. int tot=0;
  48. while(isroot(now)) s[++tot]=now,now=fa;//先拿栈存储链
  49. s[++tot]=now;
  50. while(tot) pushdown(s[tot--]);//从上往下下放
  51. now=s[1];
  52. for(;isroot(now);Rotate(now))
  53. if(isroot(fa))
  54. Rotate(identity(now)^identity(fa)?now:fa);
  55. }
  56. void access(int now)//在辅助树中打通一条到原树的链
  57. {
  58. for(int las=0;now;las=now,now=fa)
  59. splay(now),rs=las,updata(now);
  60. }
  61. void evert(int now)//将节点提至原树根节点
  62. {
  63. access(now),splay(now),Reverse(now);//打通,丢上去,翻转
  64. }
  65. void split(int u,int v)//把链抽进辅助树(可以不在一颗辅助树)
  66. {
  67. evert(u),access(v),splay(v);//放至根,打通,丢上去
  68. }
  69. int findroot(int now)//寻找原树根节点
  70. {
  71. access(now),splay(now);
  72. while(ls) now=ls;
  73. return now;
  74. }
  75. void link(int u,int v)//连边
  76. {
  77. evert(u);//搞到根
  78. if(findroot(v)!=u)//保证不在一颗树
  79. par[u]=v;//只连了虚边
  80. }
  81. void cat(int u,int v)//断边
  82. {
  83. split(u,v);
  84. if(ch[v][0]==u)//直接相连
  85. par[u]=ch[v][0]=0,updata(v);//双向断边注意更新
  86. }
  87. int query(int u,int v)
  88. {
  89. split(u,v);
  90. return sum[v];
  91. }
  92. void change(int u,int x)
  93. {
  94. splay(u),dat[u]=x,updata(u);
  95. }
  96. int main()
  97. {
  98. scanf("%d%d",&n,&m);
  99. for(int i=1;i<=n;i++) scanf("%d\n",dat+i);
  100. for(int opt,u,v,i=1;i<=m;i++)
  101. {
  102. scanf("%d%d%d",&opt,&u,&v);
  103. if(opt==0) printf("%d\n",query(u,v));
  104. else if(opt==1) link(u,v);
  105. else if(opt==2) cat(u,v);
  106. else change(u,v);
  107. }
  108. return 0;
  109. }

六、特别感谢

FlashHu的博客

高级数据结构【林厚从】

网上许多大神的博客

七、updata

① 事实上,因为\(splay\)会旋转,所以并不一定是真实被连接的边,只是满足了深度关系

我们可以想一想这两种\(cat\)方式的区别

  1. void cat(int u,int v)
  2. {
  3. evert(u);
  4. access(v);
  5. splay(v);
  6. ch[v][0]=par[u]=0;
  7. }
  1. void cat(int u,int v)
  2. {
  3. evert(u);
  4. access(v);
  5. ch[u][1]=par[v]=0;
  6. }

2018.8.11

Link Cat Tree (连喵树) 学习笔记的更多相关文章

  1. 线段树学习笔记(基础&进阶)(一) | P3372 【模板】线段树 1 题解

    什么是线段树 线段树是一棵二叉树,每个结点存储需维护的信息,一般用于处理区间最值.区间和等问题. 线段树的用处 对编号连续的一些点进行修改或者统计操作,修改和统计的复杂度都是 O(log n). 基础 ...

  2. zkw线段树学习笔记

    zkw线段树学习笔记 今天模拟赛线段树被卡常了,由于我自带常数 \(buff\),所以学了下zkw线段树. 平常的线段树无论是修改还是查询,都是从根开始递归找到区间的,而zkw线段树直接从叶子结点开始 ...

  3. 仙人掌&圆方树学习笔记

    仙人掌&圆方树学习笔记 1.仙人掌 圆方树用来干啥? --处理仙人掌的问题. 仙人掌是啥? (图片来自于\(BZOJ1023\)) --也就是任意一条边只会出现在一个环里面. 当然,如果你的图 ...

  4. P3690 【模板】Link Cut Tree (动态树)

    P3690 [模板]Link Cut Tree (动态树) 认父不认子的lct 注意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> v ...

  5. LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板

    P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...

  6. Splay伸展树学习笔记

    Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...

  7. Treap-平衡树学习笔记

    平衡树-Treap学习笔记 最近刚学了Treap 发现这种数据结构真的是--妙啊妙啊~~ 咳咳.... 所以发一发博客,也是为了加深蒟蒻自己的理解 顺便帮助一下各位小伙伴们 切入正题 Treap的结构 ...

  8. 【刷题】洛谷 P3690 【模板】Link Cut Tree (动态树)

    题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor ...

  9. JSOI2008 Blue Mary开公司 | 李超线段树学习笔记

    题目链接:戳我 这相当于是一个李超线段树的模板qwqwq,题解就不多说了. 代码如下: #include<iostream> #include<cstdio> #include ...

随机推荐

  1. HDSF读写文件

    HDFS 读取文件 HDFS的文件读取原理,主要包括以下几个步骤: 1.首先调用FileSystem对象的open方法,其实获取的是一个DistributedFileSystem的   实例. 2.D ...

  2. docker理论基础

    Namespaces 命名空间(namespaces)是 Linux 为我们提供的用于分离进程树.网络接口.挂载点以及进程间通信等资源的方法.在日常使用 Linux 或者 macOS 时,我们并没有运 ...

  3. MVC模型与MTV模型

    MVC模型: MVC(Model View Controller 模型-视图-控制器)是一种Web架构的模式,它把业务逻辑.模型数据.用户界面分离开来,让开发者将数据与表现解耦,前端工程师可以只改页面 ...

  4. 关于VSCode如何缩进两个空格

    使用VSCode编写vue的时候,由于缩进问题经常报错.(默认缩进4个空格,实际规范上是两个空格) 更改VSCode的缩进格式. 但是此时你在编写代码的时候却发现任然缩进4格,此时因为vscode默认 ...

  5. Servlet生命周期与线程安全

    上一篇介绍了Servlet初始化,以及如何处理HTTP请求,实际上在这两个过程中,都伴随着Servlet的生命周期,都是Servlet生命周期的一部分.同时,由于Tomcat容器默认是采用单实例多线程 ...

  6. Xshell 清除历史记录方法

    使用电脑久了,就会清理电脑,将一些历史记录清除,使得电脑可以运行的更快,Xshell也是同样的道理.本集小编就教大家如何清除xshell的历史记录. 如何清除历史记录: 1.打开xshell,然后点击 ...

  7. java程序——从命令行接收多个数字,求和之后输出结果

    命令行参数都是字符串,必须先将其转化为数字,才能相加.以下是流程图,源代码和输出结果. 流程图: 源代码: import java.util.Scanner; public class Test { ...

  8. Python正则反向引用

    str2 ="2018-10-29"c =re.sub(r"(\d{4})-(\d{2})-(\d{2})","\g<1>/\g<2 ...

  9. jmeter常用的内置变量

    1. vars   API:http://jmeter.apache.org/api/org/apache/jmeter/threads/JMeterVariables.html vars.get(& ...

  10. npm命令 VS yarn命令

    npm yarn 说明 npm init yarn init  在项目中引导创建一个package.json文件 npm install yarn install/yarn  安装所有依赖包(依据pa ...