在搞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. NDK-JNI实战教程(二) JNI官方中文资料

    声明 设计概述 JNI接口函数和指针 加载和链接本地方法 解析本地方法名 本地方法的参数 引用Java对象 全局和局部引用 实现局部引用 访问Java对象 访问基本类型数组 访问域和方法 报告编程错误 ...

  2. Android 国际化

    由于公司的项目是投放 google play store , 所以要做国际化.国际化遇到的两个大问题 字符串国际化 布局样式国际化 一:字符串国际化        解决这个问题很简单,在res目录下放 ...

  3. 你真的了解NSNotificationCenter吗?

    一:首先查看一下关于NSNotificationCenter的定义 @interface NSNotificationCenter : NSObject { @package void * __str ...

  4. ReactiveCocoa框架下的MVVM模式解读

    记录一些MVVM文章中关于ReactiveCocoa的代码: 实例一:带有分页的文章列表,根据文章类别过滤出文章的列表,可以进入文章详细页面 1:YFBlogListViewModel 首先了解关于列 ...

  5. 跨域iframe的高度自适应

    If you cannot hear the sound of the genuine in you, you will all of your life spend your days on the ...

  6. 使用 AngularJS 开发一个大规模的单页应用(SPA)

      本文的目标是基于单页面应用程序开发出拥有数百页的内容,包括认证,授权,会话状态等功能,可以支持上千个用户的企业级应用. 下载源代码 介绍 (SPA)这样一个名字里面蕴含着什么呢? 如果你是经典的S ...

  7. Nexus Repository Manager OSS 代理 p2 源

    用maven管理构建Eclipse RCP项目时,可能会用到p2源: http://download.eclipse.org/releases/mars/ 内网用户肯定希望能通过nexus服务器代理, ...

  8. 初识Go

    意外关注到一位牛人的微信,提到了2014年他推荐的编程语言是Go.于是乎饶有兴趣的淘了一本书<Go语言程序设计>,学习起来. 第一章的练习题我的答案如下: // Copyright © 2 ...

  9. 如何删除或重置spfile中的参数

    在ORACLE中,修改spfile中的参数一般非常容易,那么如何删除spfile中的参数呢? 下面我们用一个案例来介绍一下,如何删除spfile中的参数,一种方法就是创建对应的pfile,删除对应的参 ...

  10. mvn archetype:create和mvn archetype:generate

    create is deprecated in maven 3.0.5 and beyond,在maven3.0.5以上版本舍弃了create,使用generate生成项目 before:mvn ar ...