在搞LCT之前,我们不妨再看看喜闻乐见的树链剖分。

树链剖分有一道喜闻乐见的例题:NOI2015 软件包管理器

如果你看懂题目了,你就会明白它是叫你维护一个树,这棵树是不会动的,要兹磁子树求和,子树修改,树上路径求和,树上路径修改。

树链剖分就是把一个树剖分成像这样的东西:

一棵树用一坨重链组成,重链之间用轻链连接。

对于树上的每一个点,它和子树大小最大的那个的根节点在同一重链,其他儿子另成一条新重链。

这样可以证明每个点到根至多只有log级这么多段的连续的重链。

然后我们把连续的一坨重链用线段树维护一下。

代码(常数非常大

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
#define SIZ 266666
namespace segt
{
#define LC(x) ((x)<<1)
#define RC(x) (LC(x)+1)
int M=131072,ls[SIZ],rs[SIZ],tag[SIZ],sum[SIZ];
inline bool avb(int x)
{
return 1<=x&&x<=2*M;
}
void pd(int x)
{
if(!avb(x)||tag[x]==-1) return;
if(avb(LC(x))) tag[LC(x)]=tag[x];
if(avb(RC(x))) tag[RC(x)]=tag[x];
sum[x]=(rs[x]-ls[x]+1)*tag[x]; tag[x]=-1;
}
void upd(int x)
{
pd(LC(x)); pd(RC(x));
sum[x]=0;
if(avb(LC(x))) sum[x]+=sum[LC(x)];
if(avb(RC(x))) sum[x]+=sum[RC(x)];
}
int query(int cur,int l,int r)
{
if(!avb(cur)||l>r) return 0;
pd(cur);
if(ls[cur]==l&&rs[cur]==r) return sum[cur];
int mid=(ls[cur]+rs[cur])>>1;
int ans=query(LC(cur),l,min(mid,r))+query(RC(cur),max(mid+1,l),r);
upd(cur);
return ans;
}
void edit(int cur,int l,int r,int x)
{
if(!avb(cur)||l>r) return;
pd(cur);
if(ls[cur]==l&&rs[cur]==r) {tag[cur]=x; return;}
int mid=(ls[cur]+rs[cur])>>1;
edit(LC(cur),l,min(mid,r),x);
edit(RC(cur),max(mid+1,l),r,x);
upd(cur);
}
void init()
{
for(int i=1;i<=M;i++) sum[i+M]=0, ls[i+M]=rs[i+M]=i, tag[i+M]=-1;
for(int i=M-1;i>=1;i--)
{
tag[i]=-1; sum[i]=sum[LC(i)]+sum[RC(i)];
ls[i]=ls[LC(i)]; rs[i]=rs[RC(i)];
}
}
}
namespace lct //然而只是链剖
{
int n,S=0,ns[SIZ],fs[SIZ],ss[SIZ],
fa[SIZ],siz[SIZ],ws[SIZ],dep[SIZ],
fe[SIZ],top[SIZ],X=0,ls[SIZ];
void setc(int s,int f) //setchild
{
fa[s]=f; ++S; ns[S]=fs[f]; fs[f]=S; ss[S]=s;
}
void dfs1(int cur)
{
siz[cur]=1; ws[cur]=0;
int csc=-233;
for(int x=fs[cur];x;x=ns[x])
{
int c=ss[x];
fa[c]=cur;
dep[c]=dep[cur]+1;
dfs1(c);
if(siz[c]>csc) csc=siz[c], ws[cur]=c;
siz[cur]+=siz[c];
}
}
void dfs2(int cur,int tp)
{
fe[cur]=++X; top[cur]=tp;
if(ws[cur]) dfs2(ws[cur],tp);
for(int x=fs[cur];x;x=ns[x])
{
int c=ss[x];
if(c!=ws[cur]) dfs2(c,c);
}
ls[cur]=X;
}
void s2(int cur)
{
printf("%d\n",segt::query(1,fe[cur],ls[cur]));
segt::edit(1,fe[cur],ls[cur],0);
}
void s1(int x)
{
int u=1,v=x,ds=dep[v]-dep[u]+1,sum=0;
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2]) swap(f1,f2), swap(u,v);
//u is deeper...
sum+=segt::query(1,fe[f1],fe[u]);
segt::edit(1,fe[f1],fe[u],1);
u=fa[f1]; f1=top[u];
}
if(dep[u]>dep[v]) swap(u,v);
sum+=segt::query(1,fe[u],fe[v]);
segt::edit(1,fe[u],fe[v],1);
printf("%d\n",ds-sum);
}
}
int main()
{
int n,tmp;
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
scanf("%d",&tmp);
lct::setc(i,tmp+1);
}
lct::dfs1(1); lct::dfs2(1,1);
segt::init();
int Q;
scanf("%d",&Q);
char iu[20]; int x;
while(Q--)
{
scanf("%s%d",iu,&x);
++x;
if(iu[0]=='i') lct::s1(x);
else lct::s2(x);
}
}

稍微做一点说明吧。

dfs1这个函数就是基本的一些处理,dfs2这个函数是用来分配轻重链的。

然后子树操作就只要把这个子树里面的所有重链在线段树里面修改就行。

链的代码:

void s1(int x)
{
int u=1,v=x,ds=dep[v]-dep[u]+1,sum=0;
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2]) swap(f1,f2), swap(u,v);
//u is deeper...
sum+=segt::query(1,fe[f1],fe[u]);
segt::edit(1,fe[f1],fe[u],1);
u=fa[f1]; f1=top[u];
}
if(dep[u]>dep[v]) swap(u,v);
sum+=segt::query(1,fe[u],fe[v]);
segt::edit(1,fe[u],fe[v],1);
printf("%d\n",ds-sum);
}

在u和v不在同一条重链上时,把u和v所在重链头(就是同一条重链上深度最小的)中深度大的那个往上走,顺路更新线段树。

当u和v在同一条重链上时直接修改线段树就可以了。此时深度比较小的那个就是lca。(你想这样求lca的话我也没意见

那如果是不是边权而是点权的话就把父向边的边权设为点权就行,路经询问时记得要统计一下lca的点权,同样子树询问的时候要统计一下根节点的点权。

因为每个点到根的路径上至多有log级的重链,像这样搞的复杂度大概是O(logn*数据结构)。如果你用线段树来维护那就是O(log^2n)。但是实际复杂度往往到不了这个级别…

LCT和链剖也是类似的,也是把一个树(或森林,下文为了方便就直接说是树了)剖成若干条链,但是这里的重链和轻链有区别,每访问一个点,我们就把它到根的路径全变成重链。这个访问操作一般叫做“access”。

在LCT中我们把这个“重链”叫做Preferred Edge,把一段不能再延伸的重链叫做Preffered Path,如果结点v的子树中,最后被访问的结点在子树w中,这里w是v的儿子,那么就称w是v的Preferred Child,如果最后被访问过的结点就是v本身,那么它没有Preferred Child。

access操作看起来像这样:

LCT主体用splay维护,和链剖一样,splay里面的一条重链是存在一棵子树里的。但是这颗splay的father比较特殊…如果一个点它是这条重链splay里面最上面的一个点,那么它的父亲就是树上实际的父亲,否则就是正常splay的父亲。

剩下的事情呢如果泥会splay就比较trivial了。你要access一个点,你就把它旋到这条重链的顶上,此时它的父亲就是树上的父亲了对吧。然后把右子树接到这条重链上面的下一个点。然后把“下一个点”设为这个点,再往父亲走。

那么为什么要接右子树呢?因为我们希望中序遍历的时候是连续的一条重链…这个随意理解一下,至于右子树怎么办……爱怎么办怎么办,因为右子树的父亲不会变啊,而且右子树对应的也就是链上深度低一点的某一个点,所以并没有什么事情。

接下来还有一个基本操作叫makeroot(x),这个操作意思是让x成为整棵树的根。我们先access(x)(使x到根节点为一条重链),然后splay(x)(使x成为这条重链最上面的一个点),然后再把x这棵子树打一个翻转标记就行了。

为什么这样是可行的呢?因为x这个重链以外的东西跟根是什么并没有什么关系,只要把x这条重链翻转过来x就会在最顶上了。

然后findroot(x),找到x所在子树在原树上的根节点(因为makeroot可能会改变根节点)。这个最简单,先access(x),然后splay(x),然后一直往左孩子找,找到最左边splay一下然后返回就行。(为什么要splay回去?似乎不splay也行…复杂度玄学

然后cut(a,b),把树中a和b的连边切断,搞成两棵新树。先makeroot(a),然后access(b),然后splay(b),最后把b的左孩子和a的父亲都设为0。

还有link(a,b),把a和b中间连一条边。先makeroot(a),然后fa[a]不是为空吗,就fa[a]=b,最后在splay(a) (又是玄学

这样LCT就嘴巴写完啦。代码稍后放下一篇文章放

链剖&LCT总结的更多相关文章

  1. BZOJ-1036 树的统计Count 链剖线段树(模板)=(树链剖分+线段树)

    潇爷昨天刚刚讲完...感觉得还可以...对着模板打了个模板...还是不喜欢用指针.... 1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec Memory Lim ...

  2. CF733F Drivers Dissatisfaction【链剖】【最小生成树应用】

    F. Drivers Dissatisfaction time limit per test 4 seconds memory limit per test 256 megabytes input s ...

  3. 【洛谷】4180:【模板】严格次小生成树[BJWC2010]【链剖】【线段树维护最大、严格次大值】

    P4180 [模板]严格次小生成树[BJWC2010] 题目描述 小C最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当小C洋洋得意之时,小P又来泼小C冷水了.小P说, ...

  4. CF487E Tourists(圆方树+堆+链剖)

    本题解并不提供圆方树讲解. 所以不会圆方树的出门右转问yyb 没有修改的话圆方树+链剖. 方点的权值为点双连通分量里的最小值. 然后修改的话圆点照修,每一个方点维护一个小根堆. 考虑到可能被菊花卡死. ...

  5. BZOJ 1146 二分+链剖+线段树+treap

    思路: 恶心的数据结构题-- 首先 我们 链剖 把树 变成序列 再 套一个 区间 第K大就好了-- 复杂度(n*log^4n) //By SiriusRen #include <cstdio&g ...

  6. 链剖-进阶ing-填坑-NOIP2013-货车运输

    This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://ww ...

  7. 链剖-What you are?-大话西游-校内oj2440

    This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://ww ...

  8. 树链剖分-Hello!链剖-[NOIP2015]运输计划-[填坑]

    This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://ww ...

  9. Bzoj 1036: [ZJOI2008]树的统计Count 树链剖分,LCT

    1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 11102  Solved: 4490[Submit ...

随机推荐

  1. sqlite之WAL模式

    链接 概述 在3.7.0以后,WAL(Write-Ahead Log)模式可以使用,是另一种实现事务原子性的方法. WAL的优点 在大多数情况下更快 并行性更高.因为读操作和写操作可以并行. 文件IO ...

  2. AppCompatActivity实现全屏的问题

    前言:我的 Activity 是继承 BaseActivity , 而 BaseActivity 继承 AppCompatActivity . BaseActivity 的继承 /** * 应用程序的 ...

  3. Android Http请求框架二:xUtils 框架网络请求

    一:对Http不了解的请看 Android Http请求框架一:Get 和 Post 请求 二.正文 1.xUtils 下载地址 github 下载地址  : https://github.com/w ...

  4. 安卓开发基础之tween动画基本使用,代码教学

    xml代码块: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:andro ...

  5. 关于rails里集成测试assert_template的写法

    assert_template后面只能跟随模板文件名,不能跟随命名路径.比如routes.rb: get 'login' => 'sessions#new' 在集成测试用例里,只能写成asser ...

  6. 资源list:Github上关于大数据的开源项目、论文等合集

    Awesome Big Data A curated list of awesome big data frameworks, resources and other awesomeness. Ins ...

  7. sql server 基础教程[温故而知新三]

    子曰:“温故而知新,可以为师矣.”孔子说:“温习旧知识从而得知新的理解与体会,凭借这一点就可以成为老师了.“ 尤其是咱们搞程序的人,不管是不是全栈工程师,都是集十八般武艺于一身.不过有时候有些知识如果 ...

  8. Windows7 系统 CMD命令行,点阵字体不能改变大小以及中文乱码的问题

    之前装了oracle 11g后,发现开机速度竟然奇葩的达到了3分钟.经过旁边大神指点,说是因为oracle某个(具体不清楚)服务,在断网的时候会不断的ping网络,导致速度变慢.然后就关服务呗,然后一 ...

  9. InnoDB源码分析--缓冲池(三)

    转载请附原文链接:http://www.cnblogs.com/wingsless/p/5582063.html 昨天写到了InnoDB缓冲池的预读:<InnoDB源码分析--缓冲池(二)> ...

  10. LINUX内核笔记:自旋锁

    目录 自旋锁作用与基本使用方法? 在SMP和UP上的不同表现? 自旋锁与上下文 使用spin_lock()后为什么不能睡眠? 强调:锁什么? 参考   1.自旋锁作用与基本使用方法? 与其他锁一样,自 ...