[学习笔记]LCT进阶操作

LCT总结——应用篇(附题单)(LCT)

一般都是维护链的操作。split即可搞定。

进阶操作的话,处理好辅助树和原树的关系即可搞定。

其实,最大的区别就是,splay随便转,辅助树形态变了,但是原树形态不变,makert会让原树形态变化

 

LCT维护子树信息

真儿子会splay的时候各种变化,但是虚儿子只会在access和link的时候发生变化,其他的时候可以理解为跟着转。

以处理子树sz为例,

处理虚边子树sz,总sz(包括实边)两个

pushup注意下。

access和link注意下。

需要真正找子树信息的时候,考虑把x的后继access掉,把xsplay到根,然后虚边子树的sz就是子树sz了。(前提,makert的根不在x子树里)

模板题:

[BJOI2014]大融合

注意查询x,y的子树大小的话,makert(x),access(y),splay(y).

其实x,y直接连通,所以现在是一个长度为2的链,y是根,x是y的右儿子,然后直接(si[y]+1)*(si[x]+1)即可。(si[x]+1可以换成s[x])

#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=1e5+5;
int ch[N][2],fa[N],si[N],s[N];
int r[N];
int n,q;
bool nrt(int x){
    return (ch[fa[x]][0]==x)||(ch[fa[x]][1]==x);
}
void pushup(int x){
    if(x) s[x]=s[ch[x][0]]+s[ch[x][1]]+si[x]+1;
}
void rev(int x){
    swap(ch[x][0],ch[x][1]);
    r[x]^=1;
}
void pushdown(int x){
    if(r[x]){
        rev(ch[x][0]);rev(ch[x][1]);
        r[x]=0;
    }
}
void rotate(int x){
    int y=fa[x],d=ch[fa[x]][1]==x;
    fa[ch[y][d]=ch[x][!d]]=y;
    if(nrt(y)) ch[fa[x]=fa[y]][ch[fa[y]][1]==y]=x;
    else fa[x]=fa[y];
    ch[fa[y]=x][!d]=y;
    pushup(y);
}
int sta[N],top;
void splay(int x){
    int y=x;
    sta[++top]=y;
    while(nrt(y)) y=fa[y],sta[++top]=y;
    while(top) pushdown(sta[top--]);
    int z;
    while(nrt(x)){
        y=fa[x],z=fa[y];
        if(nrt(y)){
            rotate(((ch[z][0]==y)==(ch[y][0]==x))?y:x);
        }
        rotate(x);
    }
    pushup(x);
}
void access(int x){
    for(int y=0;x;y=x,x=fa[x]){
        splay(x);
        si[x]-=s[y];
        si[x]+=s[ch[x][1]];
        ch[x][1]=y;
        pushup(x);
    }
}
void makert(int x){
    access(x);
    splay(x);
    rev(x);
}
void link(int x,int y){
    makert(x);
    makert(y);
    si[y]+=s[x];
    s[y]+=s[x];
    fa[x]=y;
    pushup(y);
}
int query(int x,int y){
    makert(x);access(y);
    return (long long)(si[x]+1)*(si[y]+1);
}
int main(){
    rd(n);rd(q);
    char cc[2];
    int x,y;
    for(reg i=1;i<=n;++i){
        si[i]=0;s[i]=1;
    }
    while(q--){
        scanf("%s",cc+1);
        rd(x);rd(y);
        if(cc[1]=='A'){
            link(x,y);
        }else{
            printf("%d\n",query(x,y));        
        }
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/18 11:13:18
*/
大融合

 

LCT维护联通块信息(边双联通分量)

每个LCT的点,是一个边双。只不过存在一些名存实亡的点。

只能支持插入边,维护这个边双连通分量缩点之后得到的树

 

 

外面要再用一个并查集维护每个点所在的边双的代表节点。即并查集树的树根。

(这里又多了一个并查集树,可以理解为,就是为了缩点打的懒标记而已(否则暴力缩点岂不是O(n)的?))

(upda:2019.2.14:更好的解释是,其实我们扫右儿子的子树的时候,已经完成了缩点,但是右儿子的虚儿子没有记录,不能直接指向x,所以只能“懒惰”用并查集先记录一下了)

合并缩环的时候,类似于打标记,把这个实链的点的father暴力指向x,然后干掉x的右儿子,这些部分就不存在了。

名存实亡。都用x代替了。

然后pushup(x)

 

但是,可能这些实链的子树,还必须要继承到x上,怎么办呢?

由于类似于打标记,先不继承它,

因为每次处理对于链或者连通性的询问都要access,所以在这个上面做文章。

access的时候,跳father用并查集直接跳到边双的代表点p。然后顺便把自己的辅助树的father设为p。

这样,就会把子树继承上去了,并且真正意义上实现了缩点,

使得边双之间用桥连接。

 

由于有并查集,所以每次都要x=fin(x)找到真正的点再处理。

看似复杂,其实理解上,就是多了一些个用并查集维护的懒标记,用到的时候及时还原即可。

 

(但是对于维护边双联通分量,还要维护缩完点后的树的子树信息的话,,,,好像子树不能及时继承上去,估计就比较难办了。。2333)

(upda:2019.2.14:上面那个说的不对,子树信息当然可以做,因为缩点对于最高点的x的子树信息没有影响的。)

(upda:2019.3.13:emm,要看维护什么子树信息了。sz的话是不虚的,但是具体距离什么的还是没法办)

例题1:[AHOI2005]航线规划——LCT维护边双联通分量

例题2:【bzoj2959】长跑
维护的sz变成sz[x]=sz[ls]+sz[rs]+my[x]
my[x]是自己边双内部的刷卡机个数

对于2操作,找到真正的边双点,my[root]+=new-old[x],old[x]=new;

操作3直接查询链上的sz即可。

 

#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=150000+5;
int n,m;
struct node{
    int sz,fa,ch[2],r;
    int my;
}t[N];
int fa[N];
int fin(int x){
    return fa[x]==x?x:fa[x]=fin(fa[x]);
}
bool nrt(int x){
    return (t[t[x].fa].ch[0]==x)||(t[t[x].fa].ch[1]==x);
}
void rev(int x){
    swap(t[x].ch[0],t[x].ch[1]);
    t[x].r^=1;    
}
void pushdown(int x){
    if(t[x].r){
        t[x].r=0;
        rev(ls);rev(rs);
    }
}
void pushup(int x){
    if(x) t[x].sz=t[ls].sz+t[rs].sz+t[x].my;
}
void rotate(int x){
    int y=t[x].fa,d=t[y].ch[1]==x;
    t[t[y].ch[d]=t[x].ch[!d]].fa=y;
    if(nrt(y)) t[t[x].fa=t[y].fa].ch[t[t[y].fa].ch[1]==y]=x;
    else t[x].fa=t[y].fa;
    t[t[y].fa=x].ch[!d]=y;
    pushup(y);
}
int sta[N];
void splay(int x){
    int y=x,z=0;
    sta[++z]=y;
    while(nrt(y)) y=t[y].fa,sta[++z]=y;
    while(z) pushdown(sta[z--]);
    
    while(nrt(x)){
        y=t[x].fa,z=t[y].fa;
        if(nrt(y)){
            rotate(((t[y].ch[0]==x)==(t[z].ch[0]==y))?y:x);            
        }
        rotate(x);
    }
    pushup(x);
}
void access(int x){
    for(reg y=0;x;y=x,x=t[y].fa=fin(t[x].fa)){
        splay(x);rs=y;pushup(x);
    }
}
void makert(int x){
    access(x);splay(x);rev(x);
}
int findrt(int x){
    access(x);splay(x);
    pushdown(x);
    while(t[x].ch[0]) pushdown(x=t[x].ch[0]);
    splay(x);
    return x;
}
void dele(int x,int y){
    if(x) fa[x]=y,t[y].my+=t[x].my,dele(ls,y),dele(rs,y);
}
void merge(int x,int y){
    if(x==y) return;
    makert(x);
    if(findrt(y)!=x){
        t[x].fa=y;
        pushup(y);
        return;
    }
    dele(t[x].ch[1],x);
    t[x].ch[1]=0;
    pushup(x);
}
void split(int x,int y){
    makert(x);access(y);splay(y);
}
int query(int x,int y){
    makert(x);
    if(findrt(y)!=x) return -1;
    splay(y);
    return t[y].sz;
}
int now[N];
int main(){
    rd(n);rd(m);
    int x,y;    
    for(reg i=1;i<=n;++i){
        rd(x);
        now[i]=x;
        fa[i]=i;t[i].sz=x;t[i].my=x;
    }
    int c;
    for(reg i=1;i<=m;++i){
        rd(c);rd(x);rd(y);
        if(c==1){
            x=fin(x);y=fin(y);
            merge(x,y);
        }else if(c==2){
            int tmp=y-now[x];
            //cout<<" tmp "<<tmp<<endl;
            now[x]=y;
            x=fin(x);
            //cout<<" xx "<<x<<endl;
            //splay(x);
            makert(x);
            t[x].my+=tmp;
            pushup(x);
        }else{
            printf("%d\n",query(fin(x),fin(y)));
        }
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/19 9:57:28
*/
长跑

 

 

 

 

LCT维护边权(常用生成树)

边,直接变成点,因为LCT不能直接维护边。

生成树边的信息记录在点上。

 水管局长(动态最小生成树)

严格次小生成树(也可以不用LCT)

 

最小差值生成树:

肯定要把边排序,枚举固定一个较大值,

暴力的做法是不断把小的加进去,直到成为一个联通块。

考虑是否能用到上一次处理的信息,

不断加入值的时候,如果成环,那么把环上最小值替换掉即可。

这样,通过r-1的最小差值生成树,删除并添加一条边,就可以得到r的最小差值生成树

 

正确性的话,

如果存在一个更靠后的L使得[L,R]是生成树,那么必然是可以用R之前的边把一些小的边替换掉,之前肯定已经处理这种情况了。

代码:

注意:

1.pushup更新值的时候,不严格优秀就可以更新,不能只取<,否则在相等的时候会错误。

2.处理x,y的链的时候,时刻注意谁在上面,access(y)可能y就变到了x的祖先。以及findrt(y)也可能会。

 

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=50000+5;
const int M=200000+5;
const int inf=0x3f3f3f3f;
int n,m;
int tot;
int ptr;
struct node{
    int ch[2],fa,r;
    int mi,id;
    int val;
    int from;
}t[N+M];
bool nrt(int x){
    return (t[t[x].fa].ch[0]==x)||(t[t[x].fa].ch[1]==x);
}
void rev(int x){
    swap(t[x].ch[0],t[x].ch[1]);
    t[x].r^=1;
}
void pushup(int x){
    if(t[x].val<=t[ls].mi&&t[x].val<=t[rs].mi){
        t[x].mi=t[x].val;
        t[x].id=x;
    }
    else if(t[ls].mi<=t[x].val&&t[ls].mi<=t[rs].mi){
        t[x].mi=t[ls].mi;
        t[x].id=t[ls].id;
    }
    else{
        t[x].mi=t[rs].mi;
        t[x].id=t[rs].id;
    }
}
void pushdown(int x){
    if(t[x].r){
        rev(t[x].ch[1]),rev(t[x].ch[0]);
        t[x].r=0;
    }
}
void rotate(int x){
    int y=t[x].fa,d=t[y].ch[1]==x;
    t[t[y].ch[d]=t[x].ch[!d]].fa=y;
    if(nrt(y)) t[t[x].fa=t[y].fa].ch[t[t[y].fa].ch[1]==y]=x;
    else t[x].fa=t[y].fa;
    t[t[x].ch[!d]=y].fa=x;
    pushup(y);
}
int sta[N];
void splay(int x){
    int y=x,z=0;
    sta[++z]=y;
    while(nrt(y)) y=t[y].fa,sta[++z]=y;
    while(z) pushdown(sta[z--]);
    
    while(nrt(x)){
        y=t[x].fa,z=t[y].fa;
        if(nrt(y)){
            rotate(((t[z].ch[1]==y)==(t[y].ch[1]==x))?y:x);
        }
        rotate(x);
    }
    pushup(x);
}
void access(int x){
    for(reg y=0;x;y=x,x=t[x].fa){
        splay(x);
        t[x].ch[1]=y;
        pushup(x);
    }
}
void makert(int x){
    //cout<<" making "<<x<<" "<<t[x].fa<<endl;
    access(x);
    //cout<<" access end "<<endl;
    splay(x);
    //cout<<" splay end "<<endl;
    rev(x);
}
int findrt(int x){
    access(x);splay(x);
    while(t[x].ch[0]) x=t[x].ch[0];
    splay(x);
    return x;
}
int c=0;
bool in[M];
void link(int x,int y,int z,int fr){
//    cout<<" link "<<x<<" "<<y<<" "<<z<<" "<<fr<<" "<<tot<<endl;
    t[tot].val=z;t[tot].id=tot,t[tot].mi=z;t[tot].from=fr;
//    cout<<" t[4] "<<t[4].mi<<" "<<t[4].id<<endl;
    in[fr]=1;
    makert(x);
//    cout<<" sdfsdfj "<<endl;
//    cout<<findrt(y)<<endl;
//    cout<<" over "<<endl;
    if(findrt(y)!=x){
//        cout<<" add new "<<endl;
        t[x].fa=tot;
        t[tot].fa=y;
//        pushup(tot);
//        pushup(y);
        ++c;
        return;
    }
    pushup(x);
//    cout<<t[x].mi<<" "<<t[x].val<<" "<<ls<<" "<<rs<<" "<<t[4].mi<<" "<<t[4].id<<endl;
    int lp=t[x].id;
//    cout<<" kil "<<lp<<" "<<t[lp].from<<" "<<t[lp].val<<endl;
    in[t[lp].from]=0;
    splay(lp);
    t[t[lp].ch[0]].fa=0;
    t[t[lp].ch[1]].fa=0;
    
    makert(x);
    t[x].fa=tot;
    t[tot].fa=y;
}
struct edge{
    int x,y,z;
    bool friend operator <(edge a,edge b){
        return a.z<b.z;
    }
}e[M];
int main(){
    rd(n);rd(m);
    int x,y,z;
    for(reg i=1;i<=m;++i){
        rd(x);rd(y);rd(z);
        e[i].x=x;e[i].y=y;e[i].z=z;
    }
    sort(e+1,e+m+1);
    tot=n;
    int ans=0x3f3f3f3f;
    t[0].val=inf,t[0].mi=inf,t[0].id=-1;
    for(reg i=1;i<=n;++i){
        t[i].val=inf;t[i].mi=inf;t[i].id=-1;
    }
    for(reg i=1;i<=m;++i){
//        cout<<" iiii "<<i<<"---------------------------"<<" "<<e[i].z<<endl;
        ++tot;
        if(e[i].x!=e[i].y) link(e[i].x,e[i].y,e[i].z,i);
        while(in[ptr]==0) ++ptr;
        if(c==n-1){
    //        cout<<" ok "<<e[i].z<<" "<<e[ptr].z<<endl;
    //        cout<<" ptr "<<ptr<<endl;
            ans=min(ans,e[i].z-e[ptr].z);
        }
    }
    printf("%d",ans);
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/19 8:17:20
*/
最小差值生成树

 

 

 

LCT维护树上染色联通块

 [SDOI2017]树点涂色

到根路径的染色好似access操作,而且每种颜色都是新出现的,并且每一种颜色的地位相同。

用LCT维护颜色块的集合,只用到access操作,再用一个线段树维护dfs序,num[x]表示x点到根节点的路径颜色数目。

只有在虚边变成实边的时候,才会发生真正意义上的颜色改变,会对子树答案造成影响,

具体来说,变换重儿子的时候,把原来的位置的子树的每个num加上1,新儿子的num每个减掉1

操作2,num[a]+num[b]-2*num[lca]+1(分类讨论一下即可证明)

操作3,区间求max

 

这个题,LCT不算是用来维护动态树的工具,只是因为access和操作1非常相似,从而进行了一个“建模”,用LCT来拟合这个操作。

#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=1e5+5;
const int inf=0x3f3f3f3f;
int mx[4*N],ad[4*N];
int n,m;
struct edge{
    int nxt,to;
}e[2*N];
struct node{
    int fa,ch[2];
}t[N];
int hd[N],cnt;
void add(int x,int y){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    hd[x]=cnt;
}
int dfn[N],dfn2[N],df;
int fdfn[N],dep[N];
int fa[N][20];
void dfs(int x,int d){
    dfn[x]=++df;
    fdfn[df]=x;
    dep[x]=d;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(y==fa[x][0]) continue;
        t[y].fa=x;
        fa[y][0]=x;
        dfs(y,d+1);
    }
    dfn2[x]=df;
}
void pushup(int x){
    mx[x]=max(mx[x<<1],mx[x<<1|1]);
}
void pushdown(int x){
    if(!ad[x]) return;
    ad[x<<1]+=ad[x];
    ad[x<<1|1]+=ad[x];
    mx[x<<1]+=ad[x];
    mx[x<<1|1]+=ad[x];
    ad[x]=0;
}
void build(int x,int l,int r){
    if(l==r){
        mx[x]=dep[fdfn[l]];return;
    }
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
    pushup(x);
}
void upda(int x,int l,int r,int L,int R,int c){
    if(L<=l&&r<=R){
        ad[x]+=c;mx[x]+=c;return;
    }
    pushdown(x);
    if(L<=mid) upda(x<<1,l,mid,L,R,c);
    if(mid<R) upda(x<<1|1,mid+1,r,L,R,c);
    pushup(x);
}
int query(int x,int l,int r,int p){
    if(l==r){
        return mx[x];
    }
    pushdown(x);
    if(p<=mid) return query(x<<1,l,mid,p);
    return query(x<<1|1,mid+1,r,p);
}
int ask(int x,int l,int r,int L,int R){
    if(L<=l&&r<=R){
        return mx[x];
    }
    pushdown(x);
    int ret=0;
    if(L<=mid) ret=max(ret,ask(x<<1,l,mid,L,R));
    if(mid<R) ret=max(ret,ask(x<<1|1,mid+1,r,L,R));
    return ret;
}

bool nrt(int x){
    return t[t[x].fa].ch[0]==x||t[t[x].fa].ch[1]==x;
}
void rotate(int x){
    int y=t[x].fa,d=t[y].ch[1]==x;
    t[t[y].ch[d]=t[x].ch[!d]].fa=y;
    if(nrt(y)) t[t[x].fa=t[y].fa].ch[t[t[y].fa].ch[1]==y]=x;
    else t[x].fa=t[y].fa;
    t[t[x].ch[!d]=y].fa=x;
}
void splay(int x){
    while(nrt(x)){
        int y=t[x].fa,z=t[y].fa;
        if(nrt(y)){
            rotate(((t[z].ch[1]==y)==(t[y].ch[1]==x))?y:x);
        }
        rotate(x);
    }
}
int bac(int x){
    if(t[x].ch[1]==0) return 0;
    x=t[x].ch[1];
    while(t[x].ch[0]) x=t[x].ch[0];
    return x;
}
int pre(int x){
    while(t[x].ch[0]) x=t[x].ch[0];
    return x;
}
void access(int x){
    //cout<<" accessing "<<x<<endl;
    for(reg y=0;x;y=x,x=t[x].fa){
        //cout<<" x y "<<x<<" "<<y<<endl;
        splay(x);
        int lp=bac(x);
        //cout<<" bac "<<lp<<endl;
        if(lp){
            upda(1,1,n,dfn[lp],dfn2[lp],1);
        }    
        if(y){
            lp=pre(y);
        //    cout<<" pre "<<lp<<endl;
            upda(1,1,n,dfn[lp],dfn2[lp],-1);
        }
        t[x].ch[1]=y;
    }
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(reg j=19;j>=0;--j){
        if(dep[fa[x][j]]>=dep[y]){
            x=fa[x][j];
        }
    }
    if(x==y) return x;
    for(reg j=19;j>=0;--j){
        if(fa[x][j]!=fa[y][j]){
            x=fa[x][j];y=fa[y][j];
        }
    }
    return fa[x][0];
}
int main(){
    rd(n);rd(m);
    int x,y;
    for(reg i=1;i<n;++i){
        rd(x);rd(y);
        add(x,y);add(y,x);    
    }
    dfs(1,1);
    dep[0]=-1;
    for(reg j=1;j<=19;++j){
        for(reg i=1;i<=n;++i){
            fa[i][j]=fa[fa[i][j-1]][j-1];
        }
    }
//    for(reg i=1;i<=n;++i){
//        cout<<i<<" "<<dfn[i]<<" "<<fdfn[i]<<" "<<dep[i]<<endl;
//    }
    build(1,1,n);
    int c;
    while(m--){
        rd(c);
        if(c==1){
            rd(x);
            access(x);
        }
        else if(c==2){
            rd(x);rd(y);
            int anc=lca(x,y);
        //    cout<<" anc "<<anc<<endl;
            //cout<<" ---- "<<query(1,1,n,dfn[x])<<" "<<query(1,1,n,dfn[y])<<" "<<query(1,1,n,dfn[anc])<<endl;
            printf("%d\n",query(1,1,n,dfn[x])+query(1,1,n,dfn[y])-2*query(1,1,n,dfn[anc])+1);
        }else{
            rd(x);
            printf("%d\n",ask(1,1,n,dfn[x],dfn2[x]));
        }
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/19 20:08:02
*/
树点涂色

 重组病毒、LCT+SAM查询区间子串问题都是这个思路

posted @ 2018-12-18 23:00  *Miracle*  阅读(965)  评论(0编辑  收藏  举报