在搞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. Android Activity和Fragment的转场动画

    Android Activity和Fragment的转场动画 Activity转场动画 Activity的转场动画是通过overridePendingTransition(int enterAnim, ...

  2. iOS启动过程

    1.main函数 | 2.UIApplicationMain      * 创建UIApplication对象      * 创建UIApplication的delegate对象 | 3.delega ...

  3. BiliBili 第三方 Android 客户端应用源码

    基于 Material Design 的 BiliBili 第三方 Android 客户端,我们知道这个APP目前比较流行,所以大家也比较喜欢模仿,需要的参考一下 文档共享 : https://dri ...

  4. 【VLC-Android】LibVLC API简介(相当于VLC的MediaPlayer)

    前言 学新东西API很重要,这里抛砖引玉整理了一下,欢迎反馈! 声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com 农民伯伯: http://over14 ...

  5. Base64与Bitmap转换

    Base64与Bitmap互转 /** * 将base64转为bitmap * * @param string * @return */ public Bitmap stringtoBitmap(St ...

  6. 学习 OAuth2.0

    基于浏览器 访问后跳到登录页面,登录成功后跳转到授权页面,授权成功后跳转到redirect_uri指定的地址. 1.请求授权. http://localhost:8080/oauth/authoriz ...

  7. Java基础知识学习(七)

    线程(续) 线程同步 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization) 可以用两种方法同步化代码.两者 ...

  8. Java中的垃圾回收

    关于垃圾回收,主要是两个步骤: 垃圾对象的判断 垃圾对象的回收 垃圾对象的判断方法 引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何 ...

  9. knockoutjs with绑定导致unobtrusive validation失效的问题

    如果最初的时候with绑定的对象是空的,那么with绑定内部的unobtrusive validation规则在提交的时候无法生效,无法进行验证. 解决办法: 在提交的时候(或者with绑定的对象非空 ...

  10. Tomcat:基于Apache+Tomcat的集群搭建

    根据Tomcat的官方文档说明可以知道,使用Tomcat配置集群需要与其它Web Server配合使用才可以完成,典型的有Apache和IIS. 这里就使用Apache+Tomcat方式来完成基于To ...