Lct系列小结

 

这个东西欠了很久了(博客也停了很久了)

这篇博文不负责讲解Lct的基础知识(太麻烦)所以有需要的同学可以看这里

PART 1 

我们首先给一个模板

struct Splay{
    int F,s[2],rev;
    int val,sum;
    inline void NewNode(int fa,int x){
        F=fa; s[0]=s[1]=rev=0;
        val=sum=x; return ;
    }
}tree[Maxn];
inline bool Isroot(int v){return tree[tree[v].F].s[0]!=v&&tree[tree[v].F].s[1]!=v;}
inline void Pushup(int v){tree[v].sum=tree[tree[v].s[0]].sum+tree[v].val+tree[tree[v].s[1]].sum; return ;}
inline void Rev(int v){tree[v].rev^=1; swap(tree[v].s[0],tree[v].s[1]); return ;}
inline void Pushdown(int v){if(tree[v].rev){Rev(tree[v].s[0]); Rev(tree[v].s[1]); tree[v].rev=0;} return ;}
inline void Lazy(int v){if(!Isroot(v)) Lazy(tree[v].F); Pushdown(v); return ;}
inline void Set(int v,int u,int f){tree[v].s[f]=u; tree[u].F=v; return ;}
inline void Rotate(int v){
    int p=tree[v].F,g=tree[p].F;
    int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
    if(!Isroot(p)) Set(g,v,t2); else tree[v].F=g;
    Set(v,p,!t1); Set(p,S,t1); Pushup(p); return ;
}
inline void Splay(int v){
    for(Lazy(v);!Isroot(v);Rotate(v)){
        int p=tree[v].F,g=tree[p].F;
        if(!Isroot(p)) Rotate((v==tree[p].s[1])^(p==tree[g].s[1])?v:p);
    } Pushup(v); return ;
}
inline void Access(int v){for(int u=0;v;u=v,v=tree[v].F) Splay(v),tree[v].s[1]=u,Pushup(v); return ;}
inline void Make_Root(int v){Access(v); Splay(v); Rev(v); return ;}
inline void Split(int v,int u){Make_Root(v); Access(u); Splay(u); return ;}
inline void Link(int v,int u){Make_Root(v); tree[v].F=u; return ;}
inline void Cut(int v,int u){Split(v,u); tree[u].s[0]=tree[v].F=0; Pushup(u); return ;}
inline void Change(int v,int x){Splay(v); tree[v].val=x; Pushup(v); return ;}
inline void Ask(int v,int u){Split(v,u); cout<<tree[u].sum<<'\n'; return ;}
inline int Get_Root(int v){
    Splay(v); Access(v); Pushdown(v); 
    while(tree[v].s[0]) v=tree[v].s[0],Pushdown(v); 
    return v;
}

啊,确实丑的不行,但是打起来很省时间。结构体的效率也还不错。

有一个常见问题,那就是GetRoot的时候是否需要pushdown

如果只是简单的判断连通性那么是不需要的。但是如果一定要找真正的root的话就要pushdown(结构问题)

我们干脆来看一个常见的Link-Cut tree例题。

 

 

【BZOJ3282】Tree

Description

  给定N个点以及每个点的权值,要你处理接下来的M个操作。操作有4种。操作从0到3编号。点从1到N编号。
  0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。保证x到y是连通的。
  1:后接两个整数(x,y),代表连接x到y,若x到y已经连通则无需连接。
  2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。
  3:后接两个整数(x,y),代表将点x上的权值变成y。

Input

第1行两个整数,分别为N和M,代表点数和操作数。
第2行到第N+1行,每行一个整数,整数在[1,10^9]内,代表每个点的权值。
第N+2行到第N+M+1行,每行三个整数,分别代表操作类型和操作所需的量。

Output

对于每一个0号操作,你须输出X到Y的路径上点权的Xor和。

Sample Input

3 3 1231 1 20 1 2 0 1 1

Sample Output

31

Hint

1<=N,M<=300000

直接上板子233

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=300005;
inline int read(){
    char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
struct Splay{
    int F,s[2],rev;
    int val,mul;
    inline void NewNode(int fa,int x){
        F=fa; s[0]=s[1]=rev=0;
        val=mul=x; return ;
    }
}tree[Maxn];
inline bool Isroot(int v){return tree[tree[v].F].s[0]!=v&&tree[tree[v].F].s[1]!=v;}
inline void Pushup(int v){tree[v].mul=tree[tree[v].s[0]].mul^tree[v].val^tree[tree[v].s[1]].mul; return ;}
inline void Rev(int v){if(v==0) return ; tree[v].rev^=1; swap(tree[v].s[0],tree[v].s[1]); return ;}
inline void Pushdown(int v){if(tree[v].rev){Rev(tree[v].s[0]); Rev(tree[v].s[1]); tree[v].rev=0;} return ;}
inline void Lazy(int v){if(!Isroot(v)) Lazy(tree[v].F); Pushdown(v); return ;}
inline void Set(int v,int u,int f){tree[v].s[f]=u; tree[u].F=v; return ;}
inline void Rotate(int v){
    int p=tree[v].F,g=tree[p].F;
    int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
    if(!Isroot(p)) tree[g].s[t2]=v; tree[v].F=g;
    Set(v,p,t1^1); Set(p,S,t1); Pushup(p); return ;
}
inline void Splay(int v){
    for(Lazy(v);!Isroot(v);Rotate(v)){
        int p=tree[v].F,g=tree[p].F;
        if(!Isroot(p)) Rotate((v==tree[p].s[1])^(p==tree[g].s[1])?v:p);
    } Pushup(v); return ;
}
inline void Access(int v){for(int u=0;v;u=v,v=tree[v].F) Splay(v),tree[v].s[1]=u,Pushup(v); return ;}
inline void Make_Root(int v){Access(v); Splay(v); Rev(v); return ;}
inline void Split(int v,int u){Make_Root(v); Access(u); Splay(u); return ;}
inline void Link(int v,int u){Make_Root(v); tree[v].F=u; return ;}
inline void Cut(int v,int u){Split(v,u); tree[u].s[0]=tree[v].F=0; Pushup(u); return ;}
inline int Get_Root(int v){Access(v); Splay(v); while(tree[v].s[0]) Pushdown(v),v=tree[v].s[0]; return v;}
inline void Ask(int v,int u){Split(v,u); cout<<tree[u].mul<<'\n'; return ;}
inline void Change(int v,int x){Splay(v); tree[v].val=x; Pushup(v); return ;}
int main(){
    int n=read(),m=read();
    for(int i=1;i<=n;i++) tree[i].NewNode(0,read());
    for(int i=1;i<=m;i++){
        int f=read(),x=read(),y=read();
        if(f==0) Ask(x,y);
        else if(f==1){
            if(Get_Root(x)!=Get_Root(y)) Link(x,y); 
        }
        else if(f==2){
            if(Get_Root(x)==Get_Root(y)) Cut(x,y);
        }
        else Change(x,y);
    }
    return 0;
}

然后Lct可以维护一些边权的问题。为了方便起见(拒绝仙人掌)我们直接新建节点代表边

 

BZOJ4668】冷战

Description



怎样维护最早的联通性呢?

直接一点,每一条加边我们都给它赋予一条边权,大小为当前时间。

然后我们直接维护链上的最大值即可

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
	char c;int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9')rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,m;
struct Lct_Tree{
	int F,s[2],val,rev,maxx;
    inline void NewNode(int fa,int x){F=fa;val=maxx=x;s[0]=s[1]=rev=0;return ;}
}tree[1000005];
inline bool Isroot(int v){return tree[tree[v].F].s[0]!=v&&tree[tree[v].F].s[1]!=v;}
inline void Pushup(int v){tree[v].maxx=max(tree[tree[v].s[0]].maxx,max(tree[v].val,tree[tree[v].s[1]].maxx));return ;}
inline void Rev(int v){if(v==0)return ;tree[v].rev^=1;swap(tree[v].s[0],tree[v].s[1]);return ;}
inline void Pushdown(int v){if(tree[v].rev){Rev(tree[v].s[0]);Rev(tree[v].s[1]);tree[v].rev=0;}return ;}
inline void Lazy(int v){if(!Isroot(v)) Lazy(tree[v].F); Pushdown(v); return ;}
inline void Set(int v,int u,int f){tree[v].s[f]=u; tree[u].F=v; return ;}
inline void Rotate(int v){
    int p=tree[v].F,g=tree[p].F;
    int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
    if(!Isroot(p)) Set(g,v,t2); else tree[v].F=g;
    Set(v,p,!t1); Set(p,S,t1); Pushup(p); return ;
}
inline void Splay(int v){
    for(Lazy(v);!Isroot(v);Rotate(v)){
        int p=tree[v].F,g=tree[p].F;
        if(!Isroot(p)) Rotate((v==tree[p].s[1])^(p==tree[g].s[1])?v:p);
    } Pushup(v); return ;
}
inline void Access(int v){for(int u=0;v;u=v,v=tree[v].F) Splay(v),tree[v].s[1]=u,Pushup(v); return ;}
inline void Make_Root(int v){Access(v);Splay(v);Rev(v);return ;}
inline void Link(int v1,int v2){Make_Root(v1);tree[v1].F=v2;return ;}
inline int Ask(int v1,int v2){Make_Root(v1);Access(v2);Splay(v2);return tree[v2].maxx;}
inline int Find_Root(int v){while(tree[v].F)v=tree[v].F;return v;}
int main(){
	n=read();m=read();
	int last=0,cnt=0;
	for(int i=1;i<=m;i++){
		int f=read(),x=read()^last,y=read()^last;
		int fx=Find_Root(x),fy=Find_Root(y);
		if(f==0){
			cnt++;
			if(fx!=fy){
				tree[n+cnt].NewNode(0,cnt);
				Link(x,n+cnt);Link(y,n+cnt);
			}
		}
		else {
			if(fx!=fy)cout<<0<<'\n',last=0;
			else last=Ask(x,y),cout<<last<<'\n';
		}
	}
	return 0;
}

事实上,只有加边的问题多数时候可以用启发式合并的并查集维护。

时限1s,在TLE的边缘试探

bzoj表现一般

 

PART 2

由此,我们发现Lct显然可以维护最小生成树

【WC2006】水管局长是一道显然的例题

所以我们来看一道更难的(笑)

 

【HNOI2010】城市建设

Description

  PS国是一个拥有诸多城市的大国,国王Louis为城市的交通建设可谓绞尽脑汁。Louis可以在某些城市之间修建道路,在不同的城市之间修建道路需要不同的花费。Louis希望建造最少的道路使得国内所有的城市连通。但是由于某些因素,城市之间修建道路需要的花费会随着时间而改变,Louis会不断得到某道路的修建代价改变的消息,他希望每得到一条消息后能立即知道使城市连通的最小花费总和, Louis决定求助于你来完成这个任务。 

Input

  文件第一行包含三个整数N,M,Q,分别表示城市的数目,可以修建的道路个数,及收到的消息个数。
  接下来M行,第i+1行有三个用空格隔开的整数Xi,Yi,Zi(1≤Xi,Yi≤n, 0≤Zi≤50000000),表示在城市Xi与城市Yi之间修建道路的代价为Zi。
  接下来Q行,每行包含两个数k,d,表示输入的第k个道路的修建代价修改为d(即将Zk修改为d)。 

Output

  输出包含Q行,第i行输出得知前i条消息后使城市连通的最小花费总和。 

Sample Input

5 5 3
1 2 1
2 3 2
3 4 3
4 5 4
5 1 5
1 6
1 1
5 3

Sample Output

14
10
9

Hint

  对于20%的数据, n≤1000,m≤6000,Q≤6000。
  有20%的数据,n≤1000,m≤50000,Q≤8000,修改后的代价不会比之前的代价低。
  对于100%的数据, n≤20000,m≤50000,Q≤50000。

这个emm

网上好像到处都是CDQ重构图的代码,但是我不会CDQ怎么办qwq?

我们发现了修改的操作(一个修改显然可以看做是一个删除和一个添加),所以lct不能够直接维护了,怎么办?

我们有一个神奇的东西叫做线段树分治。。。这玩意儿用一个log的代价把删除变成了添加(说得不对轻喷)

然后用一个栈记下lct的操作,撤销的时候link变cut,cut变link就好了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int Maxn=50005,Maxm=80005;
inline int read(){
    char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
int n,m,Q;
int tot;
struct Edge {int a,b,val;} e[Maxm<<1];
struct node {int id,pos;} opt[Maxm];
namespace Lct{
    long long ans=0;
    int top;
    struct STACK{int id,opt;}S[Maxm];
    struct Splay{
        int F,s[2],rev;
        int id,mxid;
    } tree[Maxn+(Maxm<<1)];
    inline bool Isroot(int v){return tree[tree[v].F].s[0]!=v&&tree[tree[v].F].s[1]!=v;}
    inline void Emax(int v,int u){if(e[tree[v].mxid].val<e[tree[u].mxid].val) tree[v].mxid=tree[u].mxid; return ;}
    inline void Pushup(int v){tree[v].mxid=tree[v].id; Emax(v,tree[v].s[0]); Emax(v,tree[v].s[1]); return ;}
    inline void Rev(int v){if(v==0) return ; tree[v].rev^=1; swap(tree[v].s[0],tree[v].s[1]); return ;}
    inline void Pushdown(int v){if(tree[v].rev){Rev(tree[v].s[0]); Rev(tree[v].s[1]); tree[v].rev=0;} return ;}
    void Lazy(int v){if(!Isroot(v)) Lazy(tree[v].F); Pushdown(v); return ;}
    inline void Set(int v,int u,int f){tree[v].s[f]=u; tree[u].F=v; return ;}
    inline void Rotate(int v){
        int p=tree[v].F,g=tree[p].F;
        int t1=(v==tree[p].s[1]),t2=(p==tree[g].s[1]),S=tree[v].s[!t1];
        if(!Isroot(p)) tree[g].s[t2]=v; tree[v].F=g;
        Set(v,p,!t1); Set(p,S,t1); Pushup(p); return ;
    }
    inline void Splay(int v){
        for(Lazy(v);!Isroot(v);Rotate(v)){
            int p=tree[v].F,g=tree[p].F;
            if(!Isroot(p)) Rotate((v==tree[p].s[1])^(p==tree[g].s[1])?v:p);
        } Pushup(v); return ;
    }
    inline void Access(int v){for(int u=0;v;u=v,v=tree[v].F) Splay(v),tree[v].s[1]=u,Pushup(v); return ;}
    inline void Make_Root(int v){Access(v); Splay(v); Rev(v); return ;}
    inline void Split(int v,int u){Make_Root(v); Access(u); Splay(u); return ;}
    inline void Link(int v,int u){Make_Root(v); tree[v].F=u; return ;}
    inline void Cut(int v,int u){Split(v,u); tree[u].s[0]=tree[v].F=0; Pushup(u); return ;}
    inline int Get_Root(int v){Access(v); Splay(v); while(tree[v].s[0]) v=tree[v].s[0]; return v;}
    inline int Ask(int v,int u){Split(v,u); return tree[u].mxid;}
    inline void Sov(int id){
        int a=e[id].a,b=e[id].b;
        if(Get_Root(a)==Get_Root(b)){
            int pid=Ask(a,b);
            if(e[id].val<e[pid].val){
                Cut(e[pid].a,n+pid); Cut(e[pid].b,n+pid);
                Link(a,n+id); Link(b,n+id);
                S[++top]=(STACK){pid,0}; ans-=e[pid].val; 
                S[++top]=(STACK){id,1}; ans+=e[id].val;
            }
        }
        else{
            Link(a,n+id); Link(b,n+id);
            ans+=e[id].val; S[++top]=(STACK){id,1};
        }
        return ;
    }
    inline void Undo(int lim){
        while(top>lim){
            int id=S[top].id,opt=S[top].opt;
            --top;
            if(opt==0) Link(e[id].a,n+id),Link(e[id].b,n+id),ans+=e[id].val;
            else Cut(e[id].a,n+id),Cut(e[id].b,n+id),ans-=e[id].val;
        } return ;
    }
}
namespace Sgt{
    struct Segment_Tree{
        int L,R;
        vector<int> id;
    }tree[Maxm<<1];
    void Build(int v,int L,int R){
        tree[v].L=L; tree[v].R=R;
        if(L==R) return ;
        int mid=(L+R)>>1;
        Build(v<<1,L,mid); Build(v<<1|1,mid+1,R);
        return ;
    }
    void Cover(int v,int L,int R,int x){
        if(tree[v].L>R||tree[v].R<L) return ;
        if(L<=tree[v].L&&tree[v].R<=R)  {
            tree[v].id.push_back(x);
            return ;
        }
        Cover(v<<1,L,R,x); Cover(v<<1|1,L,R,x); return ;
    }
    void Sov(int v){
        int top=Lct::top;
        for(int i=0;i<tree[v].id.size();++i)
            Lct::Sov(tree[v].id[i]);
        if(tree[v].L==tree[v].R){
            cout<<Lct::ans<<'\n';
            Lct::Undo(top);
            return ;
        }
        Sov(v<<1); Sov(v<<1|1); 
        Lct::Undo(top);
        return ; 
    }
}
int main(){
    n=read(); m=read(); Q=read(); tot=m;
    for(int i=1;i<=m;++i){
        e[i].a=read(); e[i].b=read(); e[i].val=read();
        opt[i].id=i; opt[i].pos=1; Lct::tree[n+i].id=i;
    }
    Sgt::Build(1,1,Q);
    for(int i=1;i<=Q;++i){
        int id=read(),x=read();
        e[++tot]=e[id]; e[tot].val=x; Lct::tree[n+tot].id=tot;
        Sgt::Cover(1,opt[id].pos,i-1,opt[id].id);
        opt[id].id=tot; opt[id].pos=i;
    }
    for(int i=1;i<=m;++i)
        Sgt::Cover(1,opt[i].pos,Q,opt[i].id);
    e[0].val=-1;
    Sgt::Sov(1);
    return 0;
}

写得太丑TLE了

(BZOJ就更不用说,只有本校OJ能够安慰我的心灵)

然后,类似的题目还有

【bzoj4736/uoj#274】[清华集训2016]温暖会指引我们前行

(其实很多题目维护的lct都是为了其他部分做准备来着)

/*****************************************************我是善良的分割线************************************************/

PART 3

lct的操作 :

双联通分量

最长链

二分图

联通块个数

重心

后缀自动机<

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值