链剖&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 ...
随机推荐
- Binder中的asInterface解析
在使用AIDL通信的时候,在Stub类中都会生成一个asInterface函数,以<Android开发艺术探索>中的例子来分析,其生成的asInterface函数源码为: /** * Ca ...
- 深入.net(集合)
集合技术: 用于“批量数据”管理的重要技术,是数组技术的替代技术! 与数组技术的对比: 数组:只提供“存储的空间”,但缺乏各种数据管理措施! 集合:在数组的基础上,提供丰富的“属性”和“方法”,来方便 ...
- C阶段【02】 - 分支结构
知识重点: BOOL布尔类型 关系运算符 逻辑运算符 if语句 枚举类型 switch语句 一.BOOL布尔类型 用来存储“真”或者“假”,变了只有YES和NO两个值.YES(1)表示表达式结果为真, ...
- Windows 编程中的字符串(2)
(1)windows写日志系统 void writeDebugEventLog(TCHAR* pszMessage, WORD wType) { //#ifdef _DEBUG HANDLE hEve ...
- C#复习⑥
C#复习⑥ 2016年6月19日 23:46 Main Interfaces & Delegates 接口和委托 1.接口基本语法 public interface IList : IColl ...
- [转载]50个Demo展示HTML5无穷的魅力
Flash和HTML5的比较已经成为现在最热门的主题之一,我们不去争论哪个好哪个不好.和HTML5在很酷的动画和简单的游戏等方面一样,除非HTML5在未来几年有一些重大发展,否则Flash在富内容网页 ...
- ExtJs4.1中给列表的单元格设置颜色
如: 代码: { xtype: 'gridcolumn', ...
- Zero to One读后感
Zero to One是一本不错的书,无论你是在职场还是在创业都应该看看先.书中没有告诉你任何的职业技巧,但是很明确的告诉了你应该有的思考方式,告诉你人与机器的关系,告诉成功企业固有的模式以及你为什么 ...
- 用批处理来自动化项目编译及部署(附Demo)
阅读目录 本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 介绍 详细 处理 结论 Demo下载 介绍 一个项目从立项开始,可能就已经根据公司的配置模板将 ...
- 微信公众平台开发(三) 订阅事件(subscribe)处理
一.简介 新用户关注微信公众平台,将产生一个订阅事件,即subscribe事件,默认代码中没有对这一事件进行相应回复处理. 在新用户关注公众平台后,可能想知道该平台提供了哪些功能,以及怎样使用该平台, ...