前言
其实学过 (LCT) 好几遍了,但是代码太长且总不用导致每次用时都要重学一遍。。。
所以这次总结一篇学习笔记以免自己以后再忘。。。
预备知识:树链剖分,(Splay)
(Splay) 中基本操作戳这里
LCT的用途
树上路径询问问题大概有这么几类:
1.静态+点权(边权)不变:倍增 (O(nlogn))
2.静态+点权(边权)改变:树链剖分 (O(nlog^2n))
3.动态+点权(边权)改变:(LCT) (O(nlog^2n)) ,但常数稍大
(Link) (Cut) (Tree) ,顾名思义,它支持树的连接、断开以及点权(边权)修改。
结构
回忆在树链剖分中,树的形态不变,根据 轻重子 形成的链也是不变的。
所以每个点只需记录链首就知道它在哪个链上了。
但是 (LCT) 中树形态会变,链也会变,我们需要动态维护这些链。
对于每条链,我们要支持添加或删除节点,所以选择 (Splay)
每棵 (Splay) 树中,键值为每个点在原树中的深度(由于是一条链,所以这些深度应是连续的一段区间)
结构体中,有一个 (rt) 表示该点是否为它所在 (Splay) 树的根节点
指针 (pa) 表示该点在 (Splay) 树中的父节点(若它为 (Splay) 树的根,(pa) 为它原树中的父节点)
指针 (ch[2]) 记录该点 (Splay) 树中的子节点。
(rev) 为反转下放标记,在下面操作中会讲。
还有一些 (sum) 之类题目要求维护的东西。
操作
在 (LCT) 中,我们要实现的终极操作为 (Link) 及 (Cut) ,在实现它们的过程中需要许多附属操作。
下面就来依次介绍。
( (Splay)中重要的操作是(pushdown)下放(rev),(splay)旋转至根节点,这里不多说了,主要说说(LCT)中独有的操作。)
(1.Access) (LCT)核心操作
把一个节点与它所在原树根节点连到一条链中(即在同一棵(Splay)树中)
如图所示,将红点与根节点连到了一条链上。
进行的操作为:先把这个点所在链中深度比它大的点砍掉。
然后从这个点开始往上走,走到该链的链首(q)(链中深度最小的点),设(q)在原树中的父节点为(p),砍掉(p)所在(Splay)树中比(p)深度大的点,将(q)接在它的(Splay)树中
不断重复上一步,直到走到顶链。此时(Access)就完成了。
(2.Make \_ root)
让某节点成为它所在原树的根节点。
首先 (Access) 让它与根节点连在一条链上。
接下来,让它成为根节点,即要让它成为树中深度最小的点,只需在顶链上把它与其他节点的深度反过来,让它成为深度最小的就行了。
(画个图就清晰明了了)
可以发现,变的只是顶链上节点的深度,其它链上“相对深度”是不变的。
反转深度其实就是 (Splay) 中的 (rev) 操作。
(3.Link)
设要被连接的两个点为 (p) 和 (q)
直接将 (q) (Make \_ root) 之后连在 (p) 后面(不需连到一条链上,只需让 原树中(q) 的 (pa) 为 (p) 就行了)。
(4.Cut)
设要被断开的两个点为 (p) 和 (q)
先将 (p) (Make \_ root),然后将 (q) (Access),此时 (p) 与 (q) 同在顶链上。
接着将 (q) (splay) 成顶链 (Splay) 树上的根,判断一下 (p) 与 (q) 是否有边相连(如果有的话,此时 (q) 的左子为 (p) 且 (p) 的右子为空)
如果有边,把边断开就行了。
代码
洛谷 (P3690) 模板题
支持连接边、删除边、改点权、求路径 (xor) 和
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 300005;
struct node{
node *pa,*ch[2];
int v,sum,rt,rev;
}pool[N];
int cnt;
int sum(node *p) { return p ? p->sum : 0; }
void update(node *p) { p->sum=sum(p->ch[0])^sum(p->ch[1])^p->v; }
void pushdown(node *p){
if(!p->rev) return;
swap(p->ch[0],p->ch[1]); //splay的rev操作
if(p->ch[0]) p->ch[0]->rev^=1;
if(p->ch[1]) p->ch[1]->rev^=1;
p->rev=0;
}
void push(node *p){
if(!p->rt) push(p->pa);
pushdown(p);
}
void rotate(node *p,int ty){
node *pa=p->pa,*gp=pa->pa,*son=p->ch[ty^1];
pa->ch[ty]=son; if(son) son->pa=pa;
p->ch[ty^1]=pa; pa->pa=p;
p->pa=gp;
if(pa->rt) pa->rt=0,p->rt=1;
else gp->ch[pa==gp->ch[1]]=p;
update(pa); update(p);
}
void splay(node *p){ //将p旋转至splay树的根
push(p); //递归下放rev
while(!p->rt){
if(p->pa->rt)
rotate(p,p==p->pa->ch[1]);
else{
node *pa=p->pa,*gp=pa->pa;
int f=pa==gp->ch[0];
if(p==pa->ch[f]) rotate(p,f),rotate(p,!f);
else rotate(pa,!f),rotate(p,!f);
}
}
}
void access(node *p){
node *q=NULL;
while(p){
if(q) q->rt=0;
splay(p);
if(p->ch[1]) p->ch[1]->rt=1; //砍掉深度比p深的节点
p->ch[1]=q; //q接到p后面
update(p); //splay树加了一些节点,要update
q=p; p=p->pa;
}
}
void make_root(node *p){
access(p); splay(p);
p->rev^=1; //标记反转
}
node *find(node *p){ //寻找某节点所在splay树中深度最小的节点(即链首)
access(p); splay(p);
while(p->ch[0]) p=p->ch[0];
splay(p); //这句很重要,不加会超时(尽管我也不造为什么)
return p;
}
void link(node *p,node *q){
if(find(p)==find(q)) return;
make_root(q); q->pa=p;
}
void cut(node *p,node *q){
if(find(p)!=find(q)) return;
make_root(p); access(q); splay(q);
if(p==q->ch[0]&& !p->ch[1]){ //判断有边相连
q->ch[0]=NULL;
p->pa=NULL; p->rt=1;
}
update(q); //splay树中减去了一些节点,要update
}
int query(node *p,node *q){
make_root(p); access(q); splay(q);
return q->sum;
}
int n,m;
int main()
{
int t,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&x);
node *tmp=&pool[++cnt];
tmp->rt=1; tmp->v=tmp->sum=x; tmp->rev=0;
tmp->pa=tmp->ch[0]=tmp->ch[1]=NULL;
}
while(m--){
scanf("%d%d%d",&t,&x,&y);
if(t==0)
printf("%d
",query(&pool[x],&pool[y])); //路径x->y上的xor和
else if(t==1) link(&pool[x],&pool[y]);//连接
else if(t==2) cut(&pool[x],&pool[y]);//删除
else{ //改点权
node *p=&pool[x];
p->v=y; update(p);
while(!p->rt) {
update(p->pa);
p=p->pa;
}
}
}
return 0;
}
(update:) 清新脱俗的数组写法
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
const int N = 100005;
int n,m;
int ch[N][2],rt[N],sum[N],pa[N],rev[N],val[N];
void update(int x) { sum[x]=val[x]^sum[ch[x][0]]^sum[ch[x][1]]; }
void pushdown(int x) {
if(!rev[x]) return;
swap(ch[x][0],ch[x][1]);
rev[ch[x][0]]^=1;
rev[ch[x][1]]^=1;
rev[x]=0;
}
int st[N],top;
void push(int x){ /**/
top=0;
st[++top]=x;
while(!rt[x]) x=pa[x],st[++top]=x;
for(int i=top;i>0;i--) pushdown(st[i]);
}
void rotate(int x,int ty){
int fa=pa[x],gp=pa[fa],son=ch[x][ty^1];
ch[fa][ty]=son; if(son) pa[son]=fa;
ch[x][ty^1]=fa; pa[fa]=x;
pa[x]=gp;
if(rt[fa]) rt[fa]=0,rt[x]=1;
else ch[gp][fa==ch[gp][1]]=x;
update(fa); update(x);
}
void splay(int x){
push(x);
while(!rt[x]){
if(rt[pa[x]]) rotate(x,x==ch[pa[x]][1]);
else{
int fa=pa[x],gp=pa[fa];
int f=(fa==ch[gp][0]);
if(x==ch[fa][f]) rotate(x,f),rotate(x,!f);
else rotate(fa,!f),rotate(x,!f);
}
}
}
void access(int x){
int y=0;
while(x){
splay(x);
rt[ch[x][1]]=1;
rt[y]=0; ch[x][1]=y;
update(x);
y=x; x=pa[x];
}
}
void makeroot(int x) { access(x); splay(x); rev[x]^=1; }
int find(int x) {
access(x); splay(x);
while(ch[x][0]) x=ch[x][0];
splay(x);
return x;
}
void link(int x,int y) {
if(find(x)==find(y)) return;
makeroot(x); pa[x]=y;
}
void cut(int x,int y){
if(find(x)!=find(y)) return;
makeroot(x); access(y); splay(y);
if(ch[x][1]==0 && ch[y][0]==x) {
rt[x]=1; pa[x]=0; /**/
ch[y][0]=0;
update(y);
}
}
int Sum(int x,int y) { makeroot(y); access(x); splay(x); return sum[x]; }
int main()
{
n=read(); m=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=1;i<=n;i++) rt[i]=1;
int ty,x,y;
while(m--){
ty=read(); x=read(); y=read();
if(ty==0) printf("%d
",Sum(x,y));
else if(ty==1) link(x,y);
else if(ty==2) cut(x,y);
else {
makeroot(x);
sum[x]=sum[x]^val[x]^y;
val[x]=y;
}
}
return 0;
}