链剖&LCT总结
在搞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总结的更多相关文章
- BZOJ-1036 树的统计Count 链剖线段树(模板)=(树链剖分+线段树)
潇爷昨天刚刚讲完...感觉得还可以...对着模板打了个模板...还是不喜欢用指针.... 1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec Memory Lim ...
- CF733F Drivers Dissatisfaction【链剖】【最小生成树应用】
F. Drivers Dissatisfaction time limit per test 4 seconds memory limit per test 256 megabytes input s ...
- 【洛谷】4180:【模板】严格次小生成树[BJWC2010]【链剖】【线段树维护最大、严格次大值】
P4180 [模板]严格次小生成树[BJWC2010] 题目描述 小C最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当小C洋洋得意之时,小P又来泼小C冷水了.小P说, ...
- CF487E Tourists(圆方树+堆+链剖)
本题解并不提供圆方树讲解. 所以不会圆方树的出门右转问yyb 没有修改的话圆方树+链剖. 方点的权值为点双连通分量里的最小值. 然后修改的话圆点照修,每一个方点维护一个小根堆. 考虑到可能被菊花卡死. ...
- BZOJ 1146 二分+链剖+线段树+treap
思路: 恶心的数据结构题-- 首先 我们 链剖 把树 变成序列 再 套一个 区间 第K大就好了-- 复杂度(n*log^4n) //By SiriusRen #include <cstdio&g ...
- 链剖-进阶ing-填坑-NOIP2013-货车运输
This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://ww ...
- 链剖-What you are?-大话西游-校内oj2440
This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://ww ...
- 树链剖分-Hello!链剖-[NOIP2015]运输计划-[填坑]
This article is made by Jason-Cow.Welcome to reprint.But please post the writer's address. http://ww ...
- Bzoj 1036: [ZJOI2008]树的统计Count 树链剖分,LCT
1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 11102 Solved: 4490[Submit ...
随机推荐
- Android 查看手机中所有进程
真机测试的时候发现DDMS对进程的显示很不给力,一些进程管理工具又不显示包名. 所以就自己写了一个小程序,查看自己手机中的进程,显示当前时间和进程的包名: 程序运行截图: 布局: <Linear ...
- switch-枚举
在swift中,如果switch的枚举,可以不写default,因为系统知道有多少种情况,如果不是枚举,必须要写default enum WBComposeToolBarButtonType:Int ...
- Android项目开发实战-2048游戏
<2048>是一款比较流行的数字游戏,最早于2014年3月20日发行.原版2048首先在GitHub上发布,原作者是Gabriele Cirulli,后被移植到各个平台.这款游戏是基于&l ...
- 【代码笔记】iOS-对UIView进行截图
一,效果图. 二,工程图. 三,代码. RootViewController.m #import "RootViewController.h" @interface RootVie ...
- Android 数据库SQLite 写入SD卡
如果手机没有root,数据库文件是无法查看到的,不方便调试. 最好的办法是把数据库写进SD卡. 修改的地方有两处: 1.在你的helper类中把数据库文件名称 DATABASE_NAME 由原来的一个 ...
- CSS行高——line-height
学习资料 : http://www.cnblogs.com/dolphinX/p/3236686.html
- VS2015 Git 插件使用教程
VS2015 中继承了 Git 插件,再也不用下载 Github for Windows了. 从 团队-管理连接 中打开 团队资源管理器 克隆Repository 在 本地 Git 存储库下面点击 ...
- 2、CSS学习 - IT软件人员学习系列文章
上文我们讲了HTML,本文讲讲CSS. 上次我们讲了CSS是HTML页面的装修部分,就是各种瓷砖.粉墙.说明了CSS在HTML页面中的重要地位.没有CSS,那么HTML页面将很粗糙,就象我们的毛坯房一 ...
- Harrypotter
#include<iostream> using namespace std; int main() { ]={}; int a,b,c,d; cout<<"请输入总 ...
- 根据上一行填充本行的空白栏位,SQL处理方式
我在4年多前,写了一篇Excel处理空白Cell的文章,http://www.cnblogs.com/studyzy/archive/2010/04/07/1706203.html,其实在数据库中也会 ...