简介

  Link-cut Tree,简称LCT。

  干什么的?它是树链剖分的升级版,可以看做是动态的树剖。

  树剖专攻静态树问题;LCT专攻动态树问题,因为此时的树剖面对动态树问题已经无能为力了(动态树问题通常夹杂着树的操作,如删边与连边。这是线段树无法应对的)。

  LCT难写吗?不难写啊...

预备知识:Splay(没有接触也没有关系,打一打LCT也差不多懂了)、树链剖分。


1. LCT概念

  树链剖分把树分成若干条重链,对于每条重链,用线段树来维护信息。利用各线段树的信息来得到答案。

  模仿一下:

    • LCT把树分成若干条重链

       这是假的重链!树剖是挑选重儿子来延续重链;而LCT的重链是随缘的......

       我们先不管这里的重链是怎么确定的,因为在LCT中,重链是可以随时更改的!

    • $access(u)$,这是我们的更改操作。作用是将$u$到原树根节点的一路都变成重链,同时,经过节点将会与原本属于的重链断开,如图,这是我们要维护的原树:

      

    • 对于每条重链,我们用一棵Splay来维护信息,利用各Splay的信息来得到答案。

2. 存储方式

  LCT是怎么存储的?其中涉及到的$access$操作可能会对Splay进行删点或加点......

  我们的每条重链的Splay,都是连在一起的,但又是相互独立的!看图:

  

  橙色边为每棵Splay,灰色边表示的是Splay之间的连接边。

  每棵Splay储存照常,Splay的中序遍历即重链节点从浅到深的排列。每棵Splay内节点的关系可能和原树不同,但是与其他Splay连边的节点没有改变。

  由此,每棵Splay可以维护一条链上的信息。

  但只有每棵Splay的根节点能连向其他Splay的某个节点(灰色边)。Splay根节点$root$记录它的父亲是谁(有的Splay根节点$root$没有父亲),而它的父亲并不记录自己有这个儿子$root$。

  发现,每一个节点,都能够通过一直走父亲(不管是亲生还是认领的),走到某一个点,这个点就是上节提到的原树根节点,不同于Splay的根节点。

  


3. 基础函数(以下基本都是经典函数)

  我们需要一个函数来判断当前节点$u$是否为所属Splay的根节点:

    

  1. bool isroot(int u){
  2. return ch[fa[u]][]!=u&&ch[fa[u]][]!=u;
  3. }

  即父亲的左右儿子都不是自己,说明此节点是Splay的根节点,它的父亲并不记录自己。

  需要一个函数判断当前节点$u$是父亲节点的左儿子还是右儿子:

  

  1. int who(int u){
  2. return ch[fa[u]][]==u;
  3. }

  如果是左儿子,返回0;否则返回1。

  更新Splay信息函数,作用是收集左右儿子的信息。这里以最大值举例:

  1. void update(int u){
  2. if(!u) return;
  3. info[u]=max(w[u],max(info[ch[u][]],info[ch[u][]]));
  4. }

  

  经典的Splay翻转打标记函数reverse、单次下传函数pushdownOnce、一路下传函数pushdown、旋转函数rotate和伸展函数splay,没有什么特殊的地方:

  

  1. void reverse(int u){
  2. rev[u]^=;
  3. swap(ch[u][],ch[u][]);
  4. }
    //为u打上翻转标记
  1. void pushdownOnce(int u){
  2. if(rev[u]){
  3. if(ch[u][]) reverse(ch[u][]);
  4. if(ch[u][]) reverse(ch[u][]);
  5. rev[u]=;
  6. }
  7. }
    //单次下传
  1. void pushdown(int u){
  2. if(!isroot(u)) pushdown(fa[u]);
  3. pushdownOnce(u);
  4. }
    //从当前Splay的根节点一路下传到u,把一路的翻转都处理掉
  1. void rotate(int u){
  2. int f=fa[u],g=fa[f],c=who(u);
  3. if(!isroot(f))
  4. ch[g][who(f)]=u;
  5. fa[u]=g;
  6. ch[f][c]=ch[u][c^]; fa[ch[f][c]]=f;
  7. ch[u][c^]=f; fa[f]=u;
  8. update(f); update(u);
  9. }
    //将当前节点u旋转到父亲节点
  1. void splay(int u){
  2. pushdown(u);
  3. while(!isroot(u)){
  4. if(!isroot(fa[u]))
  5. rotate(who(fa[u])==who(u)?fa[u]:u);
  6. rotate(u);
  7. }
  8. }
    //将u旋转到当前Splay的根节点

4. 关键函数: 

  $access(u)$,更改函数,把$u$到LCT根节点一路变成一条重链,同时断开一路上原来的重儿子:

  

  1. void access(int u){
  2. for(int v=;u;v=u,u=fa[u]){
  3. splay(u);
  4. ch[u][]=v;
  5. update(u);
  6. }
  7. }

  什么意思呢?外层for循环负责迭代从$u$一直到Splay根节点的路径,同时用$v$记录是从哪里来到$u$的。

  每到达一个点$u$,我们将$u$提到树根,这时$u$的右儿子就是在原本重链上$u$的重儿子。我们把它替换成过来的节点,并更新信息即可。

  $makeRoot(u)$,换根操作,使$u$成为树的根节点:

  

  1. void makeRoot(int u){
  2. access(u);
  3. splay(u);
  4. reverse(u);
  5. }

  换根换根,实际上影响到的是哪些因素呢?

  仅仅是$u$到根节点一路上的Splay发生了父子反向,对于其它的Splay并没有影响。

  于是这样调用:

  1. 我们把$u$到根节点一路变为重链,即把它们放到一棵Splay中;
  2. 将$u$旋转到Splay的根节点;
  3. 为$u$打上翻转标记。

  这样就为$u$到根节点的信息完成了父子反向操作。

  

  $link(a,b)$,连接操作,更改树形,连接a和b两个节点,即连接a和b所在的两棵LCT(前提是a和b不在同一棵LCT中):

  

  1. void link(int a,int b){
  2. makeRoot(a);
  3. fa[a]=b;
  4. }

  我们将$a$变为$a$的LCT的根,然后将$a$的父亲设为$b$。这样就将$a$的整棵LCT连接到了$b$所在的LCT,并且$a$和$b$在定义上会相邻。

  $cut(a,b)$,切割操作,更改树形,分离a和b两个节点,即分割出两棵独立的LCT(前提是a和b在同一棵LCT中且a和b相邻):

  

  1. void cut(int a,int b){
  2. makeRoot(a);
  3. access(b);
  4. splay(b);
  5. fa[a]=;
  6. ch[b][]=;
  7. update(b);
  8. }

  我们将$a$变成树根,然后将$b$到树根(也就是$a$)一路变为重链,再将$b$旋转到所在Splay的根。

  由于$a$和$b$同在一棵Splay中且$a$一定是$b$的父亲,所以Splay中$b$的左儿子一定是$a$,断开即可,记得更新,因为有了父子关系变化。

  $isConnect(a,b)$,实现判断a和b是否在同一棵LCT中:

  1. bool isConnect(int a,int b){
  2. if(a==b) return true;
  3. makeRoot(a);
  4. access(b);
  5. splay(b);
  6. return fa[a];
  7. }

  我们将$a$变成树根,然后将$b$到树根(也就是$a$)一路变为重链,再将$b$旋转到所在Splay的根。

  如果$a$和$b$不在同一棵LCT中,执行$makeRoot(a)$后,$a$的父亲应该为空($makeRoot$最后有一个$splay(u)$的操作将$u$旋转到树根)。

  除非什么情况呢?除非a和b在同一棵LCT中,在$access(b)$并$splay(b)$后,$a$与$b$应该在同一棵Splay中,既然$b$为Splay根,那么$a$肯定不为Splay根,$a$一定有一个父亲存在。

  至此,LCT的最常用函数已经介绍完毕,下面我们来总结一下最根本的核心思想:

  可以发现$access(u)$和$splay(u)$总是配套出现,有时在前面配上$makeRoot$。这一套COMBO可以将$u$转到Splay树根,然后进行如同Splay一样的便捷操作。

   比如想求$a$到$b$的点权之和,我们可以$makeRoot(a)  +  access(b)  +  splay(b)$,此时$a$和$b$一定在同一条重链、同一棵Splay中,然后我们统计Splay中$b$和$b$的左子树的点权之和就可以了。

  


总结

  理解LCT以后就会觉得这玩意挺有意思的。一些处理信息、调用函数的思想,值得我们更多地推敲。

【LCT】一步步地解释Link-cut Tree的更多相关文章

  1. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

  2. 洛谷P3690 [模板] Link Cut Tree [LCT]

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

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

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

  4. Link Cut Tree 总结

    Link-Cut-Tree Tags:数据结构 ##更好阅读体验:https://www.zybuluo.com/xzyxzy/note/1027479 一.概述 \(LCT\),动态树的一种,又可以 ...

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

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

  6. Link Cut Tree学习笔记

    从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...

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

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

  8. Luogu 3690 Link Cut Tree

    Luogu 3690 Link Cut Tree \(LCT\) 模板题.可以参考讲解和这份码风(个人认为)良好的代码. 注意用 \(set\) 来维护实际图中两点是否有直接连边,否则无脑 \(Lin ...

  9. LG3690 【模板】Link Cut Tree (动态树)

    题意 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和.保证x到y是联通的 ...

  10. AC日记——【模板】Link Cut Tree 洛谷 P3690

    [模板]Link Cut Tree 思路: LCT模板: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 30 ...

随机推荐

  1. git bash上传代码到github

    今天,老爷机notebook开始卡了,我决定格盘重装系统. 顺便复习一下git本地命令的使用 1,配置 2先跳过一般的操作.介绍链接github远程库的操作 我的两个钥匙在C:\Users\bond\ ...

  2. Django源码学习 了解render与render_to_response

    render与render_to_response def render_to_response(template_name, context=None, content_type=None, sta ...

  3. USB的包结构及包分类

    USB的传输总是低位在前,高位在后. USB的传输方向:从设备到主机的数据为输入:从主机到设备的数据叫做输出. 1. 包结构 以同步域开始,紧跟着一个包标识符PID(Packet Identifier ...

  4. bzoj2120: 数颜色 [莫队][分块]

    Description 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜 ...

  5. SpringMVC配置实例

    一.SpringMVC概述 MVCII模式实现的框架技术 Model--业务模型(Biz,Dao...) View--jsp及相关的jquery框架技术(easyui) Contraller--Dis ...

  6. 一份关于npm的新手指南

    Node.js使得在服务器端使用JavaScript编写应用程序成为可能.它是基于V8Javascript运行时并且使用C++编写的,所以它的速度很快.最初,它旨在作为应用程序的服务器环境,但是开发人 ...

  7. 【css】盒子模型 之 弹性盒模型

    参考: http://caibaojian.com/flexbox-guide.html 待补充啊

  8. 初学Python(八)——迭代

    初学Python(八)——迭代 初学Python,主要整理一些学习到的知识点,这次是迭代. # -*- coding:utf-8 -*- from collections import Iterabl ...

  9. 4G内存服务器的MySQL配置优化

    公司网站访问量越来越大(日均超10万PV),MySQL自然成为瓶颈,关于 MySQL 的优化,最基本的是 MySQL 系统参数的优化. MySQL对于web架构性能的影响最大,也是关键的核心部分.My ...

  10. [Android]Android焦点流程代码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/7286503.html 通过View的View::focusSe ...