学习笔记——LCT
LCT长于维护链上的信息,支持树链上各种黑科技操作,但对于维护子树信息,则没有树剖来得更方便。
LCT主要维护点权,如果要维护边权,可以为边新开一个点接在两个点上。通过维护边权可以实现一些贪心的动态维护最小生成树——即加进一条边形成一个环,删掉环中最大的边。
类似的题目有:严格次小生成树,最小差值生成树,NOI2014魔法森林
对于维护字树信息,和DDP类似,再来一个数组记录虚儿子的总答案,然后在$access$和$link$时更新即可,参考大裸题:大融合
对于维护点双等连通性问题,可以再来一个并查集,因为缩点以后没法加回来,所以对于删边操作,我们考虑离线,把删边改为加边,然后LCT维护,调用直接并查集序号即可
代码:
在实现方面,要注意splay之前要$pushall$,$findroot$之后要$splay(x)$,时时刻刻记得$pushup$,在$rotate$的时候要注意特判一个$y$是否为根,access的时候要先splay再赋值右儿子
#define ls ch[x][0] #define rs ch[x][1] namespace LCT{ int ch[N+M][2],fa[N+M],rev[N+M],mx[N+M]; void init(int x){rev[x]=0,ch[x][0]=ch[x][1]=0;fa[x]=0;mx[x]=x;} void DEL(int x){ch[x][0]=ch[x][1]=fa[x]=rev[x]=mx[x]=0;val[x]=0;} int getdir(int x){return (ch[fa[x]][1]==x);}//0:left 1:right int isRoot(int x){DEL(0); return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;} void pushup(int x){ mx[x]=x; if(ls) if(val[mx[x]]<val[mx[ls]]) mx[x]=mx[ls]; if(rs) if(val[mx[x]]<val[mx[rs]]) mx[x]=mx[rs]; } void pushdown(int x){ if(rev[x]) { if(ls) swap(ch[ls][0],ch[ls][1]), rev[ls]^=1; if(rs) swap(ch[rs][0],ch[rs][1]), rev[rs]^=1; rev[x]=0; } } void pushall(int x){if(!isRoot(x)) pushall(fa[x]); pushdown(x);} void rotate(int x){ int y=fa[x],z=fa[y],dirx=getdir(x),diry=getdir(y); if(!isRoot(y)) ch[z][diry]=x; ch[y][dirx]=ch[x][dirx^1]; if(ch[x][dirx^1]) fa[ch[x][dirx^1]]=y; ch[x][dirx^1]=y; fa[y]=x; fa[x]=z; pushup(y); pushup(x); } void splay(int x){ pushall(x); int F=fa[x]; while(!isRoot(x)) { if(!isRoot(F)) { if(getdir(F)==getdir(x)) rotate(F); else rotate(x); } rotate(x); F=fa[x]; } } void access(int x){ for(int now=0;x!=0;now=x,x=fa[x]) { splay(x); ch[x][1]=now; pushup(x); } } void makeroot(int x){ access(x); splay(x); rev[x]^=1; swap(ls,rs); } int findroot(int x){ access(x); splay(x); while(ch[x][0]) { pushdown(x); x=ch[x][0]; } splay(x); return x; } void link(int x,int y){ makeroot(x); fa[x]=y; } void cut(int x,int y){ makeroot(x); access(y); splay(y); fa[x]=0; ch[y][0]=0; pushup(y); } void split(int x,int y){ makeroot(x); access(y); splay(y); pushup(y); }//y is the root }