并不对劲的LCT
LCT,是连猫树(link-cat-tree)的缩写。它是树链剖分和splay的结合版本。
由于有很多关于LCT的文章以及这并不是对劲的文章,并不对劲的人并不打算讲得太详细。
推荐:详细的LCT->
想必大家都知道splay+树剖=LCT
splay虽然常数较大,但是它好写好调(大部分操作都可以把左右边界转上去然后直接操作),而且还能维护序列。
树剖是将一棵树切分成很多条链,再将它们首尾相接拼成一个序列。可以用线段树来维护序列,进行区间操作,不过并不能插入和删除。
那么用splay来维护区间是不是就可以插入和删除了呢?想必是可以的。但是插入和删除会导致子树大小有变化,这样轻重链剖分就不适用了。不过,和splay类似地,虽然听上去很玄学,但是期望复杂度是可以被证明为log级别的;和splay类似地,并不对劲的人并不会证明。
讲讲几个操作吧:
1.上/下传标记,判断是左/右儿子
void mark(int u){if(u)swap(ls,rs),re[u]^=;}
void pu(int u){sum[]=,sum[u]=sum[ls]^sum[rs]^key[u],sum[]=;}
void pd(int u){if(re[u])mark(ls),mark(rs),re[u]=;}
int getso(int u){return son[fa[u]][]==u?:;}
和splay没什么区别。为了防止不小心pushup(0),pushup时可以在计算前后都强行令sum[0]=0。
总结push up&down出现的地方:
pushup:rotate,access(从下往上);
pushdown:splay,getroot(从上往下);
2.判断一个点是否为splay的根
int notrt(int u){return son[fa[u]][]==u||son[fa[u]][]==u;}
splay直接判断有没有父亲(我)。但是LCT是由很多棵对应一条重链的splay组成的。这样【同一树的不同splay】为了与【同一splay(父、子关系都有)】和【不同树(父、子关系都没有)】区分,是有【认父(我)不认子(你)】的规定。也就是说,splay的根可能有父亲(我),但是它的父亲(我)未必认它这个儿子(你)。
3.旋转(rotate)
void rot(int u)
{
int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),rson=son[u][l^];
if(notrt(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][l^]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
pu(rson),pu(fu),pu(u),pu(ffu);
}
和splay区别不大。但是被旋转点u的祖父可能和u不在同一棵splay上,要判断旋转点的父亲是不是根。
4.伸展(splay)
void splay(int u)
{
int v=u;top=,st[++top]=v;while(notrt(v))st[++top]=v=fa[v];
while(top)pd(st[top--]);
while(notrt(u)){int fu=fa[u];if(notrt(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
}
在普通的splay中,只有从根走到某个点才能将这个点伸展,这样肯定经过了【根到某点的路径】,那么路径上的所有标记应该已经被下传了。但是LCT中,并不会从根走到那个点。也就是说,那个点上面可能有未下传的标记。所以要先下传【根到某点的路径(是splay的根!!)】上的所有标记再进行下一步。
5.将【根到某点的路径】变成重链(access)
void acs(int u){for(int v=;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
无论怎么改重链,它们都会还在同一棵树里,所以不用改父亲(我)。
6.求出某点所在树的根(getroot)
int getrt(int u){acs(u),splay(u);while(u){pd(u);if(!ls)break;u=ls;}return u;}
splay维护的序列中的数的顺序是先走重链的DFS序,所以根在DFS序中肯定比某点靠前。把根和该点放在同一个splay后,splay维护的序列的最前面的那个数就是根。
7.将某点变成根(chroot)
void chrt(int u){acs(u),splay(u),mark(u);}
将该点access后,会发现它恰巧是这条重链的splay对应的序列中最靠后的。而根是最靠前的。所以把序列翻转就行了。
8.求某两点之间路径的和(getroad)
void getrd(int u,int v){chrt(u),acs(v),splay(v);}
让一点为根,使两点在同一条重链上。这时这棵splay维护的序列中最靠前的是根,最靠后的是另一个点,这棵splay就是想要求和的部分。
9.将两点之间连边(link)
void link(int u,int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}
将一个点转到根后判断另一个点的根是不是它。由于getroot是已经将另一个点转到splay的根,直接按【认父不认子】的规则将在树根的点接到在splay根的点上。合并之后这棵树的根是v所在splay的最靠前的点。
10.断开两点之间的边(cat)
void cat(int u,int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][]=;pu(v);}
还是将一个点转到根。先判断v是不是在以u为根的树上(这时v已经在splay根上了),再判断它们在splay的序列中是否相邻。两条都满足就可以断了,要把父子关系都断干净。splay根的儿子情况改变,所以需要pushup。
完整代码如下,可以在洛谷p3690提交:
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define maxn 300010
#define ls son[u][0]
#define rs son[u][1]
using namespace std;
inline int read()
{
int x=,f=;
char ch=getchar();
while(isdigit(ch)== && ch!='-')ch=getchar();
if(ch=='-')f=-,ch=getchar();
while(isdigit(ch))x=x*+ch-'',ch=getchar();
return x*f;
}
inline void write(int x)
{
int f=;char ch[];
if(!x){puts("");return;}
if(x<){putchar('-');x=-x;}
while(x)ch[++f]=x%+'',x/=;
while(f)putchar(ch[f--]);
putchar('\n');
}
struct LCT
{
int fa[maxn],son[maxn][],sum[maxn],key[maxn],re[maxn],st[maxn],top;
void mark(int u){if(u)swap(ls,rs),re[u]^=;}
void pu(int u){sum[]=,sum[u]=sum[ls]^sum[rs]^key[u],sum[]=;}
void pd(int u){if(re[u])mark(ls),mark(rs),re[u]=;}
int getso(int u){return son[fa[u]][]==u?:;}
int notrt(int u){return son[fa[u]][]==u||son[fa[u]][]==u;}
void rot(int u)
{
int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),rson=son[u][l^];
if(notrt(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][l^]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
pu(rson),pu(fu),pu(u),pu(ffu);
}
void splay(int u)
{
int v=u;top=,st[++top]=v;while(notrt(v))st[++top]=v=fa[v];
while(top)pd(st[top--]);
while(notrt(u)){int fu=fa[u];if(notrt(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
pu(u);
}
void acs(int u){for(int v=;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
int getrt(int u){acs(u),splay(u);while(u){pd(u);if(!ls)break;u=ls;}return u;}
void chrt(int u){acs(u),splay(u),mark(u);}
void getrd(int u,int v){chrt(u),acs(v),splay(v);}
void link(int u,int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}
void cat(int u,int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][]=;pu(v);}
}t;
int main()
{
int n=read(),q=read(),f,x,y;
for(int i=;i<=n;i++)t.key[i]=read();
while(q--)
{
f=read(),x=read(),y=read();
if(f==)t.getrd(x,y),write(t.sum[y]);
if(f==)t.link(x,y);
if(f==)t.cat(x,y);
if(f==)t.splay(x),t.key[x]=y;
}
return ;
}
当然,如果乐意卡常的话…
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define ls son[u][0]
#define rs son[u][1]
#define rep(i,x,y) for(register int i=(x);i<=(y);i++)
#define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
#define Int register int
#define maxn 300010
using namespace std;
inline int read()
{
Int x=,f=;
char ch=getchar();
while(isdigit(ch)== && ch!='-')ch=getchar();
if(ch=='-')f=-,ch=getchar();
while(isdigit(ch))x=x*+ch-'',ch=getchar();
return x*f;
}
inline void write(Int x)
{
Int f=;char ch[];
if(!x){puts("");return;}
if(x<){putchar('-');x=-x;}
while(x)ch[++f]=x%+'',x/=;
while(f)putchar(ch[f--]);
putchar('\n');
}
int fa[maxn],son[maxn][],sum[maxn],key[maxn],re[maxn],st[maxn],top;
void mark(Int u){if(u)swap(ls,rs),re[u]^=;}
void pu(Int u){sum[u]=sum[ls]^sum[rs]^key[u];}
void pd(Int u){if(re[u])mark(ls),mark(rs),re[u]=;}
int getso(Int u){return son[fa[u]][]!=u;}
int nort(Int u){return son[fa[u]][]==u || son[fa[u]][]==u;}
void rot(Int u)
{
Int fu=fa[u],ffu=fa[fu],l=getso(u),r=l^,fl=getso(fu),rson=son[u][r];
if(nort(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][r]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
pu(fu),pu(u);
}
void splay(Int u)
{
Int v=u;top=;st[++top]=v;while(nort(v))st[++top]=v=fa[v];
while(top)pd(st[top--]);
while(nort(u)){if(nort(fa[u]))rot(getso(u)^getso(fa[u])?u:fa[u]);rot(u);}
}
void acs(Int u){for(Int v=;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
void chrt(Int u){acs(u),splay(u),mark(u);}
int getrt(Int u){acs(u),splay(u);while(u){pd(u);if(!ls)return u;u=ls;}}
void getrd(Int u,Int v){chrt(u),acs(v),splay(v);}
void link(Int u,Int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}
void cut(Int u,Int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][]=;}
int main()
{
Int n=read(),q=read(),f,x,y;
rep(i,,n)key[i]=read();
while(q--)
{
f=read(),x=read(),y=read();
if(!f)getrd(x,y),write(sum[y]);
if(f==)link(x,y);
if(f==)cut(x,y);
if(f==)splay(x),key[x]=y;
}
return ;
}
不知道这个模板能不能在5分钟之内打完?据说最快7分钟?
并不对劲的LCT的更多相关文章
- 一堆LCT板子
搞了一上午LCT,真是累死了-- 以前总觉得LCT高大上不好学不好打,今天打了几遍感觉还可以嘛= =反正现在的水平应付不太难的LCT题也够用了,就这样好了,接下来专心搞网络流. 话说以前一直YY不出来 ...
- 动态树之LCT(link-cut tree)讲解
动态树是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作.其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT( ...
- 在此为LCT开一个永久的坑
其实我连splay都还不怎么会. 今天先抄了黄学长的bzoj2049,以后一定要把它理解了. 写LCT怎么能不%数据结构大神yeweining呢?%%%chrysanthemums %%%切掉大森林 ...
- 【BZOJ2157】旅游 LCT
模板T,SB的DMoon..其实样例也是中国好样例...一开始不会复制,yangyang:找到“sample input”按住shift,按page down.... #include <ios ...
- 【BZOJ3669】[Noi2014]魔法森林 LCT
终于不是裸的LCT了...然而一开始一眼看上去这是kruskal..不对,题目要求1->n的路径上的每个点的两个最大权值和最小,这样便可以用LCT来维护一个最小生成路(瞎编的...),先以a为关 ...
- 【BZOJ1180】: [CROATIAN2009]OTOCI & 2843: 极地旅行社 LCT
竟然卡了我....忘记在push_down先下传父亲的信息了....还有splay里for():卡了我10min,但是双倍经验还是挺爽的,什么都不用改. 感觉做的全是模板题,太水啦,不能这么水了... ...
- 【BZOJ3282】Tree LCT
1A爽,感觉又对指针重怀信心了呢= =,模板题,注意单点修改时splay就好,其实按吾本意是没写的也A了,不过应该加上能更好维护平衡性. ..还是得加上好= = #include <iostre ...
- BZOJ2888 资源运输(LCT启发式合并)
这道题目太神啦! 我们考虑他的每一次合并操作,为了维护两棵树合并后树的重心,我们只好一个一个的把节点加进去.那么这样一来看上去似乎就是一次操作O(nlogn),但是我们拥有数据结构的合并利器--启发式 ...
- LCT裸题泛做
①洞穴勘测 bzoj2049 题意:由若干个操作,每次加入/删除两点间的一条边,询问某两点是否连通.保证任意时刻图都是一个森林.(两点之间至多只有一条路径) 这就是个link+cut+find roo ...
随机推荐
- python学习笔记--python简介
一.什么是python? python是一种面向对象.解释型的高级程序语言.python具有语法简洁.易于学习.功能强大,可扩展性强,跨平台等诸多特点.1989年开始开发,于1991年发布第一个公开发 ...
- POJ 1080 Human Gene Functions 【dp】
题目大意:每次给出两个碱基序列(包含ATGC的两个字符串),其中每一个碱基与另一串中碱基如果配对或者与空串对应会有一个分数(可能为负),找出一种方式使得两个序列配对的分数最大 思路:字符串动态规划的经 ...
- CodeIgniter与Zend Acl结合实现轻量级权限控制
CodeIgniter与Zend Acl结合实现轻量级权限控制 Tag :CodeIgniter Zend Acl 权限控制 1. Zend_Acl简介 Zend_Acl 为权限管理提供轻量并灵活的访 ...
- HDU 5695 Gym Class
拓扑排序. #include<cstdio> #include <iostream> #include<cstring> #include<cmath> ...
- 洛谷——P1608 路径统计
P1608 路径统计 题目描述 “RP餐厅”的员工素质就是不一般,在齐刷刷的算出同一个电话号码之后,就准备让HZH,TZY去送快餐了,他们将自己居住的城市画了一张地图,已知在他们的地图上,有N个地方, ...
- 标准格式包含: 私有属性 无参构造 有参构造 setter 和getter 需求中的方法 需求一: 员工类Employee 属性:姓名name,工号id,工资salary 行为:显示所有成员信息的方法show() 需求二: 动物类Animal 属性:姓名name,年龄age 行为:吃饭
// 员工类 public class Employee { private String name; private int id; private double salary; public ...
- hp 88a加粉
http://v.youku.com/v_show/id_XNzEzODEwNzMy.html
- LINUX下安装和配置WEBLOGIC10.0.3
weblogic for linux安装 首先声明,我参考了某位原创者的笔记,加以整理的.安装1. 安装前的准备工作1.1 首先请确认您要安装的Weblogic版本所在的平台已通过了BEA的认证,完整 ...
- zerorpc使用时报错:No handlers could be found for logger "zerorpc.channel"
问题如题:安装方法参考 http://www.cnblogs.com/shengulong/p/7887586.html ,安装完后,使用时出现如题的错误 解决办法: 1.zerorpc本身依赖很多三 ...
- linux设备驱动归纳总结
前言: (总结已经基本写完,这段时间我会从新排版和修正.错误总会有的,望能指正!) 前段时间学习了嵌入式驱动,趁着没开始找工作,这段时间我会每天抽出时间来复习. 我的总结是根据学习时的笔记(李杨老师授 ...