动态树(LCT)
学习笔记: Yang Zhe QTREE解法的一些研究 , OIWiki
时间复杂度: (mathcal{O(nlogn)})
前置知识: splay, 树链剖分
理解
-
可以简单把 LCT 理解成用一些 Splay 来维护动态的树链剖分,一期实现动态树上的区间操作。
-
对于每条实链,建一个 Splay 来维护整个链的区间。
-
每棵 Splay 维护原树中的一条路径,且中序遍历这颗 Splay 得到的点序列,从前到后对应原树 从上到下的一条路径。
-
在 LCT 中,每棵 Splay 的根节点的父亲节点指向原树中 这条链 的父亲节点。
变量声明
ch[N][2]
左右儿子f[N]
⽗亲指向sum[N]
路径权值和val[N]
点权tag[N]
翻转标记
函数声明
Splay 系函数
Get(x)
获取 是父亲的哪个儿子。Splay(x)
通过和 Rotate 操作联动实现把 旋转到当前 Splay 的根。Rotate(x)
将 向上旋转一层的操作。
新操作
Access(x)
把从根到 的所有点放在⼀条实链里,使根到 成为一条实路径,并且在同一棵 Splay 里。 只有此操作是必须实现的,其他操作视题目而实现。IsRoot(x)
判断 是否是所在树的根。Update(x)
在Access
操作之后,递归地从上到下Pushdown
更新信息。MakeRoot(x)
使 点成为其所在树的根。Link(x, y)
在 两点间连一条边。Cut(x, y)
把 两点间边删掉。Find(x)
找到 所在树的根节点编号。Fix(x, v)
修改 的点权为 。Split(x, y)
提取出 间的路径,方便做区间操作。
模板
//LCT
struct LCT{
int ch[maxn][2],f[maxn],tag[maxn],val[maxn],sum[maxn];
inline int getch(int x){return ch[f[x]][1]==x;}
inline int isroot(int x){return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;}
inline void pushdown(int p){
if(tag[p]){
if(ch[p][0])swap(ch[ch[p][0]][0],ch[ch[p][0]][1]),tag[ch[p][0]]^=1;
if(ch[p][1])swap(ch[ch[p][1]][0],ch[ch[p][1]][1]),tag[ch[p][1]]^=1;
tag[p]=0;
}
}
inline void pushup(int x){
if(ch[x][0])pushdown(ch[x][0]);
if(ch[x][1])pushdown(ch[x][1]);
sum[x]=(val[x]+sum[ch[x][0]]+sum[ch[x][1]])%mod;
}
void update(int p){
if (!isroot(p))update(f[p]);
pushdown(p);
}
inline void rotate(int x){
int y=f[x],z=f[y],k=getch(x);
if (!isroot(y))ch[z][ch[z][1]==y]=x;
ch[y][k]=ch[x][!k],f[ch[x][!k]]=y;
ch[x][!k]=y,f[y]=x,f[x]=z;
pushup(x),pushup(y);
}
//Splay(x) 通过和 Rotate 操作联动实现把 x 旋转到当前 Splay 的根
inline void splay(int x){
update(x);
for(int fa;fa=f[x],!isroot(x);rotate(x)){
if(!isroot(fa))rotate(getch(fa)==getch(x)?fa:x);
}
pushup(x);
}
//Access(x) 把从根到 x 的所有点放在一条实链里,使根到 x 成为一条实路径,并且在同一棵Splay里
//返回值y 则此时x到当前根的路径恰好构成一个Splay,且该Splay的根为y
inline int access(int x){
int p;
for(p=0;x;p=x,x=f[x]){
splay(x);ch[x][1]=p;pushup(x);
}
return p;
}
//MakeRoot(x) 使x点成为其所在树的根
inline void makeroot(int p){
access(p);splay(p);
swap(ch[p][0],ch[p][1]);
tag[p]^=1;
}
//在x,y两点间连一条边
inline void link(int x,int p){
makeroot(x);
splay(x);
f[x]=p;
}
// split 拿出一颗Splay 维护x到y的路径
inline void split(int x,int y){
makeroot(x);access(y);splay(y);
}
//把x,y两点间的边删掉
inline void cut(int x,int p){
split(x,p);
if(ch[p][0]==x&&!ch[x][1])ch[p][0]=f[x]=val[x]=0;
}
//如果有 Find(x)==Find(y),则说明x,y两点在一棵树上,相互连通
inline int find(int p){
access(p);splay(p);
while(ch[p][0])p=ch[p][0];
splay(p);
return p;
}
inline int lca(int x,int y){access(x);return access(y);}
void print(int x) {
if (!x) return;
pushdown(x);
printf("(");
print(ch[x][0]);
printf(")%d[", x);
print(ch[x][1]);
printf("]");
}
}st;