LCT总结

类比树剖,树剖是通过静态地把一棵树剖成若干条链然后用一种支持区间操作的数据结构维护(比如线段树、树状数组),而LCT是动态地去处理这个问题。

大家都知道树剖用线段树维护,而LCT用\(splay\)维护。实际上同一条重链上的所有点才会被放在一棵\(splay\)中,而我们需要同时处理这若干条重链对应的若干棵\(splay\)之间的关系。因为一条重链上的每个点的深度互异,所以\(splay\)以深度\(dep\)为关键字。

我们规定一棵\(splay\)的根的\(fa\)为这条重链链顶节点在原树中的父亲节点。(前面的那个\(fa\)指的是\(splay\)中的\(fa\))

而显然认了这个父亲之后,父亲不会认这个儿子:很简单,这两个点不在同一条重链上,所以父亲的左右儿子一定没有它。由此可以写一个判断一个点是不是根节点(当前\(splay\)的根节点)的函数:

bool isroot(int x){
return ch[0][fa[x]]!=x&&ch[1][fa[x]]!=x;
}

先讲LCT最重要的一个操作:\(access(x)\),表示把\(x\)节点到\(x\)所在树(连通块)的根节点之间的路径全部变成重路径。

void access(int x){
for (int y=0;x;y=x,x=fa[x])
splay(x),ch[1][x]=y,pushup(x);
}

相当于是说要把这些在路径上的点全部搞到一棵\(splay\)里面去。

从下往上做,每次把\(x\)splay到当前\(splay\)的根后,这个\(x\)就会处于一个很微妙的位置:它的右儿子\(rs\)应该会接上这条重链下面的一部分,因为这条重链下面一部分的深度显然会大于\(x\)。而所谓“下面的一部分”就是我们上一个处理的\(x\),这也就是为什么y=x,x=fa[x]了。

最开始的\(x\)的下面不应该接上任何东西,所以\(y\)的初值赋为0。

再就是另一个基本操作:\(makeroot(x)\),表示把\(x\)节点设为\(x\)所在树(连通块)的根节点。

我们先把\(x\)access了,即连接了\(x\)与根节点,然后再把\(x\)splay到根,那么显然这个\(x\)肯定会是\(splay\)中的最后一个元素(因为它深度最大),所以我们在\(x\)这里打一个\(rev\)标记(\(splay\)区间翻转操作)即可。

void makeroot(int x){
access(x);splay(x);reverse(x);
}

剩下的操作就很简单了。

\(findroot(x)\),找到\(x\)节点所在树(连通块)的根节点。用这个操作可以维护连通性。

\(findroot(x)=access(x)+splay(x)+\)找到最左边(深度最小)的那个节点。

请注意暴跳了\(x\)之后要把\(x\)splay一下。你可能会问为什么我以前这样写没有被卡,那就请移步[Luogu4230]连环病原体

int findroot(int x){
access(x);splay(x);
while (ch[0][x]) x=ch[0][x];
splay(x);
return x;
}

\(split(x,y)\),抠出\(x\)到\(y\)的路径,抠完以后\(y\)是\(splay\)的根。

\(split(x,y)=makeroot(x)+access(y)+splay(y)\)

void split(int x,int y){
makeroot(x);access(y);splay(y);
}

\(cut(x,y)\),砍断\(x\)到\(y\)的边。

\(cut(x,y)=split(x,y)+\)x的父亲和y的左儿子置0。

(split之后这一棵\(splay\)中就只剩\(x\)和\(y\)两个点了,所以才可以这么搞)

void cut(int x,int y){
split(x,y);fa[x]=ch[0][y]=0;
}

\(link(x,y)\),连接\(x\)到\(y\)的边。

\(cut(x,y)=makeroot(x)+fa[x]=y\)

void link(int x,int y){
makeroot(x);fa[x]=y;
}

注意:

split请务必保证两个点联通

cut请务必保证两个点直接相连

link请务必保证两个点不联通

不然出现玄学错误谁都救不了你了

模板

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 300005;
int n,m,opt,a,b,fa[N],ch[2][N],sum[N],val[N],rev[N],Stack[N],top;
int gi()
{
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
bool son(int x)
{
return x==ch[1][fa[x]];
}
bool isroot(int x)
{
return ch[0][fa[x]]!=x&&ch[1][fa[x]]!=x;
}
void pushup(int x)
{
sum[x]=sum[ch[0][x]]^sum[ch[1][x]]^val[x];
}
void reverse(int x)
{
if(!x)return;
swap(ch[0][x],ch[1][x]);rev[x]^=1;
}
void pushdown(int x)
{
if(!rev[x])return;
reverse(ch[0][x]);reverse(ch[1][x]);
rev[x]=0;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],c=son(x);
ch[c][y]=ch[c^1][x];if (ch[c][y]) fa[ch[c][y]]=y;
fa[x]=z;if (!isroot(y)) ch[son(y)][z]=x;
ch[c^1][x]=y;fa[y]=x;pushup(y);
}
void splay(int x)
{
Stack[top=1]=x;
for (int i=x;!isroot(i);i=fa[i])
Stack[++top]=fa[i];
while (top) pushdown(Stack[top--]);
for (int y=fa[x];!isroot(x);rotate(x),y=fa[x])
if (!isroot(y)) son(x)^son(y)?rotate(x):rotate(y);
pushup(x);
}
void access(int x)
{
for (int y=0;x;y=x,x=fa[x])
splay(x),ch[1][x]=y,pushup(x);
}
void makeroot(int x)
{
access(x);splay(x);reverse(x);
}
int findroot(int x)
{
access(x);splay(x);
while (ch[0][x]) x=ch[0][x];
splay(x);return x;
}
void split(int x,int y)
{
makeroot(x);access(y);splay(y);
}
void cut(int x,int y)
{
split(x,y);fa[x]=ch[0][y]=0;
}
void link(int x,int y)
{
makeroot(x);fa[x]=y;
}
int main()
{
n=gi();m=gi();
for (int i=1;i<=n;i++) val[i]=gi();
while (m--)
{
opt=gi();a=gi();b=gi();
if (opt==0)
split(a,b),printf("%d\n",sum[b]);
if (opt==1)
if (findroot(a)!=findroot(b))
link(a,b);
if (opt==2)
if (findroot(a)==findroot(b))
cut(a,b);
if (opt==3)
splay(a),val[a]=b,pushup(a);
}
return 0;
}

LCT维护子树信息

LCT还可以维护子树和,但仅限于维护而不支持修改

一般而言就是用一个数组\(sz[u]\)维护每个节点的所有虚子树信息之和。

所以虚子树+实子树+自己=整个子树

那么在所有可能导致虚儿子关系变化的地方都要更新\(sz[u]\)

其实仅有的不同只有三个函数:\(pushup,access,link\)

放一下代码

void pushup(int x)
{
sum[x]=sum[ch[0][x]]+sum[ch[1][x]]+val[x]+sz[x];
}
void access(int x)
{
for (int y=0;x;y=x,x=fa[x])
splay(x),sz[x]+=sum[ch[1][x]]-sum[y],ch[1][x]=y,pushup(x);
}
void link(int x,int y)
{
makeroot(x);makeroot(y);fa[x]=y;sz[y]+=sum[x];pushup(y);
}

具体可参考BJOI2014大融合cogs2701动态树

LCT总结的更多相关文章

  1. 一堆LCT板子

    搞了一上午LCT,真是累死了-- 以前总觉得LCT高大上不好学不好打,今天打了几遍感觉还可以嘛= =反正现在的水平应付不太难的LCT题也够用了,就这样好了,接下来专心搞网络流. 话说以前一直YY不出来 ...

  2. 动态树之LCT(link-cut tree)讲解

    动态树是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作.其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT( ...

  3. 在此为LCT开一个永久的坑

    其实我连splay都还不怎么会. 今天先抄了黄学长的bzoj2049,以后一定要把它理解了. 写LCT怎么能不%数据结构大神yeweining呢?%%%chrysanthemums  %%%切掉大森林 ...

  4. 【BZOJ2157】旅游 LCT

    模板T,SB的DMoon..其实样例也是中国好样例...一开始不会复制,yangyang:找到“sample input”按住shift,按page down.... #include <ios ...

  5. 【BZOJ3669】[Noi2014]魔法森林 LCT

    终于不是裸的LCT了...然而一开始一眼看上去这是kruskal..不对,题目要求1->n的路径上的每个点的两个最大权值和最小,这样便可以用LCT来维护一个最小生成路(瞎编的...),先以a为关 ...

  6. 【BZOJ1180】: [CROATIAN2009]OTOCI & 2843: 极地旅行社 LCT

    竟然卡了我....忘记在push_down先下传父亲的信息了....还有splay里for():卡了我10min,但是双倍经验还是挺爽的,什么都不用改. 感觉做的全是模板题,太水啦,不能这么水了... ...

  7. 【BZOJ3282】Tree LCT

    1A爽,感觉又对指针重怀信心了呢= =,模板题,注意单点修改时splay就好,其实按吾本意是没写的也A了,不过应该加上能更好维护平衡性. ..还是得加上好= = #include <iostre ...

  8. BZOJ2888 资源运输(LCT启发式合并)

    这道题目太神啦! 我们考虑他的每一次合并操作,为了维护两棵树合并后树的重心,我们只好一个一个的把节点加进去.那么这样一来看上去似乎就是一次操作O(nlogn),但是我们拥有数据结构的合并利器--启发式 ...

  9. LCT裸题泛做

    ①洞穴勘测 bzoj2049 题意:由若干个操作,每次加入/删除两点间的一条边,询问某两点是否连通.保证任意时刻图都是一个森林.(两点之间至多只有一条路径) 这就是个link+cut+find roo ...

  10. 链剖&LCT总结

    在搞LCT之前,我们不妨再看看喜闻乐见的树链剖分. 树链剖分有一道喜闻乐见的例题:NOI2015 软件包管理器 如果你看懂题目了,你就会明白它是叫你维护一个树,这棵树是不会动的,要兹磁子树求和,子树修 ...

随机推荐

  1. Nginx Rewrite规则详解

    Rewrite规则含义就是某个URL重写成特定的URL,从某种意义上说为了美观或者对搜索引擎友好,提高收录量及排名等. Rewrite规则的最后一项参数为flag标记,支持的flag标记主要有以下几种 ...

  2. [Swift]UIKit学习之警告框:UIAlertController和UIAlertView

    Important: UIAlertView is deprecated in iOS 8. (Note that UIAlertViewDelegate is also deprecated.) T ...

  3. iOS 点击屏幕空白区隐藏键盘方法

    iOS开发中,经常要用到输入框,可默认情况下,输入框出来之后,除非点击键盘上面的“Done”或“Next”按钮才能将其隐藏.站在用户体验的角度上看,这种情况很不友好,尤其是不能突显苹果操作的便捷性. ...

  4. jsp去除空行的web.xml配置

    在jsp中我们引入的标签,例如jstl的标签,循环遍历等等,可能会产生很多空行,其实也没什么,不会影响展示,但是空行多多少少会影响性能,这是我们只需要在web.xml中配置一下我们就可以很简单的去掉, ...

  5. Android虚拟机安装

    由于虫师那边的源估计到期了,我又找了一波. 打开SDK Manager.exe, 就在安卓目录下. 点击Tools--Options进入配置页面 mirrors.neusoft.edu.cn 配置如下 ...

  6. 使用C#解决部分Win8.1系统窗口每隔几秒失去焦点的问题【转】

    使用了Win8.1 With Update 1后,发现重启系统后,当前激活的窗口总是每隔几秒失去焦点,过0.5~1秒焦点回来,导致输入无法正常工作,严重影响使用心情和效率. 在网上找了很久,也没找到相 ...

  7. hadoop源码调试

    原文地址:http://www.cnblogs.com/end/archive/2011/04/26/2029497.html 在使用hadoop的时候,可能遇到各种各样的问题,然而由于hadoop的 ...

  8. 左连接条件与where条件的区别

    Sql 查询语句应用左连接时的链接条件中经常加一些常量值在里面如: "On a.id= b.id and b.is_del =0 and b.is_old =1" 这种条件如果加在 ...

  9. 如何更改Ubuntu的root密码

    安装Ubuntu系统时,只提示了设定用户密码,该密码可用于普通用户暂时获取root的权限,执行一些需要root权限的操作,而没有要求我们设置root密码,在需要用到root密码时,却想不起来,很尴尬啊 ...

  10. FusionCharts 2D柱状图和折线图的组合图

    1.设计思路 (1)了解组合图的特性以及用法,选用图的类型: (2)设计出两根柱子和两根折线,分开展示. 2.设计步骤 (1)设计页面 Column2DLine.html: <!DOCTYPE ...