树上数据结构——LCT

概述

LCT是一种强力的树上数据结构,支持以下操作:

  1. 链上求和
  2. 链上求最值
  3. 链上修改
  4. 子树修改
  5. 子树求和
  6. 换根
  7. 断开树上一条边
  8. 连接两个点,保证连接后仍然是一棵树。

基本概念

LCT是对树的实链剖分,即把所有边划分为实边和虚边

类似于重链剖分,每个点连向子节点中的实链至多只会有一条,把这条实边连向的儿子叫做实儿子

把一些实边连接的点构成的链叫做实链,容易发现实链之间没有共同点

需要注意的是一个不在实边上的点(一些叶节点)也视为一条没有实边的实链

于是实链之间一定是用虚边链接的

要涉及动态删连边操作,于是使用splay来维护一条实链,splay是LCT的辅助树

此处splay的深度按中序遍历严格递增

由于用splay维护,LCT的实边是动态的,可以改变

核心操作

access(x):让x到根节点的所有边均为实边,并且x没有实儿子

这个推荐flash_hu的博客,简单易懂

稍微说一下,每次操作先把当前要连的点splay到当前splay的根,由于splay中深度按中序遍历递增,此时根的右儿子一定是之前连的实链,需要去掉

于是把之前的点连到当前根的右儿子就行了

注意此时一些\(fa,son,isroot\)之类的信息改变了,需要\(push\)_\(up\)

  1. void access(int x){
  2. for(int y=0;x;y=x,x=fa[x]){ //y是之前的根,x是当前需要连的点
  3. splay(x); ch[x][1]=y;
  4. push_up(x);
  5. }
  6. }

其他操作

  1. makeroot

    换根操作

    access(x)之后x是深度最大的点

    所以splay(x)之后,x在splay中一定没有右子树,这个时候翻转整个splay,所有点的深度就都倒过来了,x成为深度最小的点,即为根节点

    1. void pushr(int x){
    2. swap(ch[x][0],ch[x][1]);
    3. r[x]^=1;
    4. }
    5. void makeroot(int x){
    6. access(x); splay(x);
    7. pushr(x);
    8. }
  2. findroot

    找所在树的树根,可以用来判断两点之间的连通性(两点所在树相同则有唯一相同根

    1. int findroot(int x){
    2. access(x);splay(x);
    3. while(c[x][0]) push_down(x),x=ch[x][0];//寻找深度最小的点,此处push_down是为了x到跟的标记放完,好判连通性
    4. splay(x);//多多splay有益健康
    5. return 0;
    6. }
  3. split

    把一条路径拉成一个splay

    1. void spilt(int x,int y){
    2. makeroot(x);access(y);
    3. splay(y);
    4. }
  4. link

    连一条边,保证连完还是一棵树

    不保证合法:

    1. int link(int x,int y){
    2. makeroot(x);
    3. if(findroot(y)==x) return 0;
    4. fa[x]=y; //把x作为y的儿子
    5. return 1;
    6. }

    保证合法:

    1. void link(int x,int y){
    2. makeroot(x);
    3. fa[x]=y;
    4. }

    此处连的边是虚边(感受到实链剖分的方便了罢

  5. cut

    断边

    保证存在:

    1. void cut(int x,int y){
    2. split(x,y);
    3. fa[x]=ch[y][0]=0;
    4. }

    不存在此边的时候是什么情况呢?

    先把x给\(makeroot\)到根

    1. x和y不连通 (\(findroot\))

    2. 在同一splay中而没有直接连边 (\(f[y]==x\)且\(!c[y][0]\))

      (考虑其他的点在哪里,在findroot之后x到了根节点,如果x和y之间有点,只能是在y到根的路径上或者y的左儿子上)

    1. int cut(int x,int y){
    2. makeroot(x);
    3. if(findroot(y)!=x||fa[y]!=x||ch[y][0]) return 0;
    4. fa[y]=ch[x][1]=0;
    5. push_up(x);
    6. return 1;
    7. }
  6. nroot

    naiive的操作,判断此点是否不是当前splay的根节点

    1. int nroot(int x){
    2. return (ch[fa[x]][1]==x||ch[fa[x]][0]==x);
    3. }
  7. splay 的特殊性

    此处splay的标记一定要从上往下放,也就是先开个栈把标记放完再旋转

完整模板

  1. #include<iostream>
  2. #include<algorithm>
  3. #include<cstdio>
  4. #include<cstring>
  5. using namespace std;
  6. #define reg register int
  7. #define il inline
  8. #define ls ch[x][0]
  9. #define rs ch[x][1]
  10. int read(){
  11. int x=0,pos=1;char ch=getchar();
  12. for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
  13. for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
  14. return pos?x:-x;
  15. }
  16. const int N = 400025;
  17. int fa[N],ch[N][2],v[N],s[N],st[N],r[N];
  18. il int nroot(int x){
  19. return ch[fa[x]][1]==x||ch[fa[x]][0]==x;
  20. }
  21. il int get(int x){
  22. return ch[fa[x]][1]==x;
  23. }
  24. il void push_up(int x){
  25. s[x]=v[x]^s[ls]^s[rs];
  26. }
  27. il void pushr(int x){
  28. swap(ls,rs);r[x]^=1;
  29. }
  30. il void push_down(int x){
  31. if(r[x]){
  32. if(ls) pushr(ls);
  33. if(rs) pushr(rs);
  34. r[x]=0;
  35. }
  36. }
  37. il void rotate(int x){
  38. int f=fa[x],g=fa[f],kx=get(x),kf=get(f);
  39. if(nroot(f)) ch[g][kf]=x;
  40. fa[x]=g;
  41. /*if(ch[x][kx^1])*/ fa[ch[x][kx^1]]=f;
  42. ch[f][kx]=ch[x][kx^1];
  43. fa[f]=x;ch[x][kx^1]=f;
  44. push_up(f);push_up(x);
  45. }
  46. il void push(int x){
  47. if(nroot(x)) push(fa[x]);
  48. push_down(x);
  49. }
  50. il void splay(int x){
  51. int f,g;
  52. push(x);//fuctional stack
  53. while(nroot(x)){
  54. f=fa[x],g=fa[f];
  55. if(nroot(f)){
  56. rotate(get(x)==get(f)?f:x);
  57. }
  58. rotate(x);
  59. }
  60. }
  61. il void access(int x){
  62. for(reg y=0;x;y=x,x=fa[x]){
  63. splay(x);rs=y;push_up(x);
  64. }
  65. }
  66. il void makeroot(int x){
  67. access(x);splay(x);pushr(x);
  68. }
  69. il void spilt(int x,int y){
  70. makeroot(x);
  71. access(y);splay(y);
  72. }
  73. il int findroot(int x){
  74. access(x);splay(x);
  75. while(ls) x=ls;
  76. splay(x);
  77. return x;
  78. }
  79. il void link(int x,int y){
  80. makeroot(x);
  81. if(findroot(y)!=x) fa[x]=y;
  82. }
  83. il void cut(int x,int y){
  84. makeroot(x);
  85. if(findroot(y)==x&&fa[y]==x&&!ch[y][0]){
  86. fa[y]=ch[x][1]=0;
  87. push_up(x);
  88. }
  89. }
  90. int n,m;
  91. int main(){
  92. n=read();m=read();
  93. for(reg i=1;i<=n;i++){
  94. v[i]=read();
  95. }
  96. for(reg i=1;i<=m;++i){
  97. int opt=read(),x=read(),y=read();
  98. if(opt==0){
  99. spilt(x,y);printf("%d\n",s[y]);
  100. }else if(opt==1){
  101. link(x,y);
  102. }else if(opt==2) cut(x,y);
  103. else splay(x),v[x]=y;
  104. }
  105. return 0;
  106. }

之后可能会补自己做的LCT题(咕


在创作本文的过程中,参考了以下文章:

树上数据结构——LCT的更多相关文章

  1. 2019暑期金华集训 Day5 树上数据结构

    自闭集训 Day5 树上数据结构 前置知识 点分治 边分治 树链剖分 LCT Top Tree LCT时间复杂度 线段树每次查询是严格\(\log n\)的,然而splay维护连续段的时候,如果每次查 ...

  2. 模板—数据结构—LCT

    模板—数据结构—LCT Code: #include <cstdio> #include <algorithm> using namespace std; #define N ...

  3. ZROI 19.08.01 树上数据结构

    1.总览 LCT 链分治(树剖) 点/边分治 2.点分治 一棵树,点有\(0/1\),多次修改,询问最远的两个\(1\)距离. 建出点分树,每个子树用堆维护:①最远的\(1\)距离:②它的每个儿子的① ...

  4. ZROI 暑期高端峰会 A班 Day4 树上数据结构

    FBI Warning:本文含有大量人类的本质之一. 你经历过绝望吗? [ZJOI2007]捉迷藏 询问树上最远黑点对. 动态边分治可以比点分治少一个 \(\log\). bzoj3730 咕了. [ ...

  5. CF1111E Tree 动态规划+LCT

    这个题的思路非常好啊. 我们可以把 $k$ 个点拿出来,那么就是求将 $k$ 个点划分成不大于 $m$ 个集合的方案数. 令 $f[i][j]$ 表示将前 $i$ 个点划分到 $j$ 个集合中的方案数 ...

  6. OI题目类型总结整理

    ## 本蒟蒻的小整理qwq--持续更新(咕咕咕) 数据结构 数据结构 知识点梳理 数据结构--线段树 推荐yyb dalao的总结--戳我 以后维护线段树还是把l,r写到struct里面吧,也别写le ...

  7. zhengrui集训D1-D5笔记

    Day_1 计数 它咕掉了 Day_1 序列数据结构 它咕掉了 Day_2 线性代数 高斯消元\Large{高斯消元}高斯消元 普通版:略 模质数:求逆 模合数:exgcd 逆矩阵\Large{逆矩阵 ...

  8. 【BZOJ-2555】SubString 后缀自动机 + LinkCutTree

    2555: SubString Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 1936  Solved: 551[Submit][Status][Di ...

  9. The Last Week

    二轮省选前的最后一周了呢. 一路走到这里,真的很希望能继续走下去. 好好调整一下状态,争取能有机会买D吧(虽然现在似乎D也没什么用了 day1 多项式 多项式ln 多项式exp day2 数据结构 L ...

随机推荐

  1. spring加载bean流程解析

    spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步.不过作为朝夕相处的框架,我们必须得明白一个问题就是sprin ...

  2. stage_ros的world文件配置方法

    官方文档参阅:http://rtv.github.io/Stage/modules.html stage_ros是一个基于stage的2D模拟器,用于ROS的仿真测试.虽然现在越来越多的人在使用gaz ...

  3. wps10.1中将txt转为excel

    1.将想要保存的内容保存为txt格式,用分隔符分隔好(包括空格.制表符.英文的逗号以及分号四种). 2.打开wps 3.点击数据->导入数据,选择刚才的txt文件 4.一步步操作,即可.

  4. 2019nc#9

    题号 标题 已通过代码 题解/讨论 通过率 团队的状态 A The power of Fibonacci 点击查看 进入讨论 69/227 未通过 B Quadratic equation 点击查看 ...

  5. POJ - 2516 Minimum Cost 每次要跑K次费用流

    传送门:poj.org/problem?id=2516 题意: 有m个仓库,n个买家,k个商品,每个仓库运送不同商品到不同买家的路费是不同的.问为了满足不同买家的订单的最小的花费. 思路: 设立一个源 ...

  6. 复习+dfs

    1.参考:https://www.cnblogs.com/ckxkexing/p/8466097.html 这道题自己写过,还写过blog,但是第二次写还是不会. (于是开坑,想做做dfs的整理.

  7. 天梯杯 L2-003. 月饼

    L2-003. 月饼 时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不 ...

  8. 多级树的深度遍历与广度遍历(Java实现)

    目录 多级树的深度遍历与广度遍历 节点模型 深度优先遍历 广度优先遍历 多级树的深度遍历与广度遍历 深度优先遍历与广度优先遍历其实是属于图算法的一种,多级树可以看做是一种特殊的图,所以多级数的深/广遍 ...

  9. Spring Boot2 系列教程(二)创建 Spring Boot 项目的三种方式

    我最早是 2016 年底开始写 Spring Boot 相关的博客,当时使用的版本还是 1.4.x ,文章发表在 CSDN 上,阅读量最大的一篇有 43W+,如下图: 2017 年由于种种原因,就没有 ...

  10. 分布式Id - redis方式

    本篇分享内容是关于生成分布式Id的其中之一方案,除了redis方案之外还有如:数据库,雪花算法,mogodb(object_id也是数据库)等方案,对于redis来说是我们常用并接触比较多的,因此主要 ...