这个东西欠了很久了(博客也停了很久了)
这篇博文不负责讲解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的操作 :
双联通分量
最长链
二分图
联通块个数
重心
后缀自动机<