LCT复习

LCT,虚实链剖分。支持连边和断边操作。Tarjan制造。

[HNOI2010]弹飞绵羊

当然这题分块可以做,常数小,但是LCT更无脑。
建立一个虚拟的弹飞节点(n+1),初始化时对于一个点假如再弹一次就弹飞了,连n+1,否则连弹到的点。维护一个(size)查询就直接split找size就行了。修改的时候对应的link,cut就解决了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=300010;
int n,m,a[N],fa[N],ch[N][3],rev[N],size[N],stack[N],k,x,y;
bool nroot(int x){
    return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
}
void update(int x){
    size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
}
void rever(int x){
    swap(ch[x][1],ch[x][0]);
    rev[x]^=1;
}
void pushdown(int x){
    if(rev[x]){
        if(ch[x][0])rever(ch[x][0]);
        if(ch[x][1])rever(ch[x][1]);
        rev[x]=0;
    }
}
bool son(int x){
    return ch[fa[x]][1]==x;
}
void rotate(int x){
    int y=fa[x],z=fa[y],a=son(x),b=son(y),c=ch[x][!a];
    if(nroot(y))ch[z][b]=x;
    fa[x]=z;
    if(c)fa[c]=y;
    ch[x][!a]=y;
    ch[y][a]=c;
    fa[y]=x;
    update(y);
    update(x);
}
void splay(int x){
    int a=x,top=0;
    stack[++top]=a;
    while(nroot(a))stack[++top]=a=fa[a];
    while(top)pushdown(stack[top--]);
    int y,z;
    while(nroot(x)){
        y=fa[x];z=fa[y];
        if(nroot(y))
            if(son(x)==son(y))rotate(y);
            else rotate(x);
        rotate(x);
    }
}
void access(int x){
    for(int y=0;x;x=fa[y=x]){
        splay(x),ch[x][1]=y,update(x);
    }
}
void makeroot(int x){
    access(x);
    splay(x);
    rever(x);
}
int findroot(int x){
    access(x);
    splay(x);
    while(ch[x][0])pushdown(x),x=ch[x][0];
    return x;
}
void split(int x,int y){
    makeroot(x);
    access(y);
    splay(y);
}
void link(int x,int y){
    makeroot(x);
    if(findroot(y)!=x)fa[x]=y;
}
void cut(int x,int y){
    makeroot(x);
    if(findroot(y)==x&&fa[x]==y&&!ch[x][1]){
        fa[x]=ch[y][0]=0;
        update(y);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n+1;i++)size[i]=1;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]+i>n)link(i,n+1);
        else link(i,a[i]+i);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&k);
        
        if(k==1){
            scanf("%d",&x);
            x+=1;
            split(x,n+1);
            printf("%d
",size[n+1]-1);
        }
        else{
            scanf("%d%d",&x,&y);
            x+=1;
            if(a[x]+x>n)cut(x,n+1);
            else cut(x,x+a[x]);
            a[x]=y;
            if(x+a[x]>n)link(x,n+1);
            else link(x,x+a[x]);
        }
    }
    return 0;
}

[SDOI2017]树点涂色

这题并没有用到link,cut。用到了access。我们该怎么求答案呢?记(sum[i])为i点到跟有多少不同的颜色。然后(sum[x]+sum[y]-2*sum[lca]+1)就可以得到答案,子树最值?把点的dfn扔到线段树上就行了。怎么维护?因为只会把当前节点到根的颜色改变,自然想到access。我们把相同颜色的节点扔到一个splay中,然后操作一就是一个access了。还是不能维护?我们在access的过程中会断开当前splay的一部分,splay中连上一些点,断开时我们区间加,连接时我们区间减就行了。

#include<cstdio>
#include<algorithm>
#define MN 410001
#define lp (p<<1)
#define rp ((p<<1)|1)
using namespace std;

int read_p,read_ca;
inline int read(){
    read_p=0;read_ca=getchar();
    while(read_ca<'0'||read_ca>'9') read_ca=getchar();
    while(read_ca>='0'&&read_ca<='9') read_p=read_p*10+read_ca-48,read_ca=getchar();
    return read_p;
}
struct na{int y,ne;}b[MN<<1];
int n,m,o,l[MN],num=0,x,y,fa[MN],ch[MN][2],df[MN],lo[MN],nm=0,v[MN],de[MN],ma[MN],s[MN],pdf[MN],f[MN][20];
bool rt[MN];
inline int max(int a,int b){return a>b?a:b;}
inline void in(int x,int y){b[++num].y=y;b[num].ne=l[x];l[x]=num;}

void rot(int k){
    int p=fa[k],bo=ch[p][1]==k;
    ch[p][bo]=ch[k][!bo];
    ch[k][!bo]=p;
    fa[ch[p][bo]]=p;
    fa[k]=fa[p];
    fa[p]=k;
    if (rt[p]) rt[p]=0,rt[k]=1;else ch[fa[k]][ch[fa[k]][1]==p]=k;
    p=k;
}

void splay(int x){
    while (!rt[x]){
        if (rt[fa[x]]) rot(x);else
        if ((ch[fa[fa[x]]][1]==fa[x])==(ch[fa[x]][1]==x)) rot(fa[x]),rot(x);else rot(x),rot(x);
    }
}

void add(int p,int l,int r,int L,int R,int v){
    if (l==L&&r==R) s[p]+=v,ma[p]+=v;else{
        int mid=l+r>>1;
        if (R<=mid) add(lp,l,mid,L,R,v);else
        if (L>mid) add(rp,mid+1,r,L,R,v);else
        add(lp,l,mid,L,mid,v),add(rp,mid+1,r,mid+1,R,v);
        ma[p]=max(ma[lp],ma[rp])+s[p];
    }
}

int ask(int p,int l,int r,int k){
    if (l==r) return ma[p];
    int mid=l+r>>1;
    if (k<=mid) return ask(lp,l,mid,k)+s[p];else return ask(rp,mid+1,r,k)+s[p];
}

int ask(int p,int l,int r,int L,int R){
    if (L==l&&R==r) return ma[p];
    int mid=l+r>>1;
    if (R<=mid) return ask(lp,l,mid,L,R)+s[p];else
    if (L>mid) return ask(rp,mid+1,r,L,R)+s[p];else
    return max(ask(lp,l,mid,L,mid),ask(rp,mid+1,r,mid+1,R))+s[p];
}

int lf(int x){return ch[x][0]?lf(ch[x][0]):x;}

void acc(int x){
    for (int i=0;x;x=fa[i=x]){
        splay(x);
        if (ch[x][1]) add(1,1,nm,df[lf(ch[x][1])],lo[lf(ch[x][1])],1),rt[ch[x][1]]=1;
        rt[ch[x][1]=i]=0;if (ch[x][1]) add(1,1,nm,df[lf(ch[x][1])],lo[lf(ch[x][1])],-1);
    }
}

void dfs(int x){
    f[x][0]=fa[x];
    for (int i=1;i<20;i++) f[x][i]=f[f[x][i-1]][i-1];
    df[x]=++nm;rt[x]=1;de[x]=de[fa[x]]+1;pdf[nm]=de[x];
    for (int i=l[x];i;i=b[i].ne)
    if (b[i].y!=fa[x]) fa[b[i].y]=x,dfs(b[i].y);
    lo[x]=nm;
}

void build(int p,int l,int r){
    if (l==r) {ma[p]=pdf[l];return;}
    int mid=l+r>>1;
    build(lp,l,mid);build(rp,mid+1,r);
    ma[p]=max(ma[lp],ma[rp]);
}

int lca(int x,int y){
    if (de[x]<de[y]) swap(x,y);
    for (int i=19;i>=0;i--)
    if (f[x][i]&&de[f[x][i]]>=de[y]) x=f[x][i];
    if (x==y) return x;
    for (int i=19;i>=0;i--)
    if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

int main(){
    register int i;
    n=read();m=read();
    for (i=1;i<n;i++) x=read(),y=read(),in(x,y),in(y,x);
    dfs(1);
    build(1,1,n);
    while (m--){
        o=read();
        if (o==1) acc(read());else
        if (o==2) x=read(),y=read(),o=lca(x,y),printf("%d
",ask(1,1,n,df[x])+ask(1,1,n,df[y])-2*ask(1,1,n,df[o])+1);else
        x=read(),printf("%d
",ask(1,1,n,df[x],lo[x]));
    }
}

[WC2006]水管局长

LCT维护动态图两点最长边。
首先有一个问题,如何用LCT维护边的信息?把边看做一个点,连接两个点时,用这两个点分别连接这一条边就行了。
此题,时间倒流,先把不删的边都建好,有环怎么办?我们找到x,y路径上最长的一条边如果比当前边大,删去,在把当前边连上去。否则不连当前边。找最长边可以用如下代码实现。

void update(int now){
	int l=ch[now][0];
	int r=ch[now][1];
	mxid[now]=now;
	if(v[mxid[l]]>v[mxid[now]])mxid[now]=mxid[l];
	if(v[mxid[r]]>v[mxid[now]])mxid[now]=mxid[r];
}

然后split之后直接找根的mxid就行了。
不删的边建好后,时光倒流一个一个加边就行了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=300001;
int n,m,q,w[2000][2000],f[N],a[N],b[N],book[2000][2000],ans[N],hhh;
int mxid[N],ch[N][2],v[N],fa[N],rev[N],tot,stack[N];
struct ques{
	int u,v,type;
}qu[N];
struct edge{
	int u,v,w;
}e[N];
void update(int now){
	int l=ch[now][0];
	int r=ch[now][1];
	mxid[now]=now;
	if(v[mxid[l]]>v[mxid[now]])mxid[now]=mxid[l];
	if(v[mxid[r]]>v[mxid[now]])mxid[now]=mxid[r];
}
bool isroot(int now){
	return ch[fa[now]][1]!=now&&ch[fa[now]][0]!=now;
}
bool son(int now){
	return ch[fa[now]][1]==now;
}
void rotate(int x){
	int y=fa[x],z=fa[y],a=son(x),b=son(y),c=ch[x][!a];
	if(!isroot(y))ch[z][b]=x;fa[x]=z;
	if(c)fa[c]=y;ch[y][a]=c;
	ch[x][!a]=y;fa[y]=x;
	update(y);update(x);
}
void rever(int now){
	swap(ch[now][1],ch[now][0]);
	rev[now]^=1;
}
void pushdown(int now){
	if(rev[now]){
		if(ch[now][1])rever(ch[now][1]);
		if(ch[now][0])rever(ch[now][0]);
	}
	rev[now]=0;
}
void splay(int x){
	int a=x,top=1;stack[top]=x;
	while(!isroot(a))stack[++top]=a=fa[a];
	while(top)pushdown(stack[top--]);
	while(!isroot(x)){
		int y=fa[x],z=fa[y];
		if(!isroot(y)){
			if(son(x)==son(y))rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
void access(int x){
	for(int y=0;x;x=fa[y=x])
		splay(x),ch[x][1]=y,update(x);
}
void makeroot(int x){
	access(x);
	splay(x);
	rever(x);
}
void split(int x,int y){
	makeroot(x);
	access(y);
	splay(y);
}
int findroot(int x){
	access(x);
	splay(x);
	pushdown(x);
	while(ch[x][0])pushdown(x=ch[x][0]);
	return x;
}
void link(int x,int y){
	makeroot(x);
	if(findroot(y)!=x)
		fa[x]=y;
}
void cut(int x,int y){
	makeroot(x);
	if(findroot(y)==x&&fa[x]==y&&!ch[x][1])
		ch[y][0]=fa[x]=0,update(y);
}
int find(int x){
	if(f[x]==x)return x;
	else return f[x]=find(f[x]);
}
int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
}
bool cmp(edge a,edge b){
	return a.w<b.w;
}
int main(){
	n=read();m=read();q=read();
	for(int i=1;i<=n;i++)f[i]=i;
	for(int i=1;i<=m;i++)
		e[i].u=read(),e[i].v=read(),e[i].w=read(),
		w[e[i].u][e[i].v]=w[e[i].v][e[i].u]=e[i].w;
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=q;i++){
		qu[i].type=read(),qu[i].u=read(),qu[i].v=read();
		if(qu[i].type==2)book[qu[i].u][qu[i].v]=book[qu[i].v][qu[i].u]=1;
	}
	tot=n;
	for(int i=1;i<=m;i++){
		int u=e[i].u,V=e[i].v;
		if(book[u][V]||book[V][u])continue;
		int fu=find(u),fv=find(V);
		if(fu!=fv){
			tot++;
			a[tot]=u;b[tot]=V;
			v[tot]=e[i].w;
			link(tot,a[tot]);
			link(tot,b[tot]);
			f[fu]=fv;
		}
	}
	for(int i=q;i>=1;i--){
		if(qu[i].type==2){
			int u=qu[i].u,V=qu[i].v;
			int fu=find(u),fv=find(V);
			if(fu==fv){
				split(u,V);
				int mx=mxid[V];
				if(v[mx]>w[qu[i].u][qu[i].v]){
					cut(a[mx],mx);
					cut(b[mx],mx);
				}
				else continue;
			}
			f[fu]=fv;
			tot++;v[tot]=w[qu[i].u][qu[i].v];
			a[tot]=qu[i].u;b[tot]=qu[i].v;
			link(tot,a[tot]);

			link(tot,b[tot]);
		}
		else{
			split(qu[i].u,qu[i].v);
			ans[++hhh]=v[mxid[qu[i].v]];
		}
	}
	for(int i=hhh;i>=1;i--)printf("%d
",ans[i]);
	return 0;
}

[ZJOI2016]大森林

获得新技能——虚点。我反正是想不出来。单点询问,区间操作的时候可以离线。像差分一样把区间分成两个点。一个插入一个删除。发现还是不好做,就要用到虚点了。预处理时每一个更换生长节点的操作我们建一个虚点接在上一个虚点上。我们跟据操作的时间顺序,每一次生长操作把生长出来的节点挂在最近的一个虚点上。我们离线扫整个区间的时候,扫到一个更改生长点的操作。就把对应的虚点挂在生长点上,到区间末尾时,在把虚点挂回原来的位置。这个过程中处理询问就行了。有一些问题,虚点这么多挂在树上不会影响询问吗?不会,我们把虚点的大小都设为0。把一个区间的生长出来的点挂在不交区间的虚点上不会错吗?不会,因为,当没有扫到虚点对应区间时,虚点回挂在上一个虚点上,又因为虚点大小为0,等价虚点不存在。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=500010;
int n,m,v[N],ch[N][3],sum[N],fa[N],size[N],now,ans[N],tot,num,nu,last,cnt,l[N],r[N],id[N];
bool isroot(int x){
    return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
}
void update(int x){
    sum[x]=v[x]+sum[ch[x][1]]+sum[ch[x][0]];
}
bool son(int x){
    return ch[fa[x]][1]==x;
}
void rotate(int x){
    int y=fa[x],z=fa[y],a=son(x),b=son(y),c=ch[x][!a];
    if(isroot(y))ch[z][b]=x;
    fa[x]=z;
    if(c)fa[c]=y;
    ch[x][!a]=y;ch[y][a]=c;fa[y]=x;
    update(y);update(x);
}
void splay(int x){
    while(isroot(x)){
        int y=fa[x];int z=fa[y];
        if(isroot(y))
            if(son(x)==son(y))rotate(y);
            else rotate(x);
        rotate(x);
    }
}
int access(int x){
    int y=0;
    for(;x;y=x,x=fa[x]){
        splay(x);ch[x][1]=y;update(x);
    }
    return y;
}
void link(int x,int y){
    splay(x);
    fa[x]=y;
}
void cut(int x){
    access(x);
    splay(x);
    fa[ch[x][0]]=0;ch[x][0]=0;
    update(x);
}
int lca(int x,int y){
    int tmp=0;
    access(x);splay(x);tmp+=sum[x];
    int t=access(y);splay(y);tmp+=sum[y];
    access(t);splay(t);tmp-=2*sum[t];
    return tmp;
}
struct ask{
    int k,num,x,y;
    friend bool operator <(const ask&a,const ask&b){
        return a.k<b.k||(a.k==b.k&&a.num<b.num);
    }
}q[N];
void newnode(int x){
    v[++tot]=x;
    sum[tot]=x;
}
int main(){
    scanf("%d%d",&n,&m);
    newnode(1);now=1;l[1]=1;r[1]=n;id[1]=1;
    newnode(0);last=2;link(2,1);
    for(int i=1;i<=m;i++){
        int k,a,b,L,R,x;
        scanf("%d",&k);
        if(k==0){
            scanf("%d%d",&a,&b);
            newnode(1);
            l[++now]=a;r[now]=b;id[now]=tot;
            q[++cnt].k=1;q[cnt].num=i-m;q[cnt].x=tot;q[cnt].y=last;
        }
        else if(k==1){
            scanf("%d%d%d",&L,&R,&x); 
     		L=max(L,l[x]);R=min(R,r[x]);
     		if(L<=R){
     			newnode(0);link(tot,last);
     			q[++cnt].k=L;q[cnt].num=i-m;q[cnt].x=tot;q[cnt].y=id[x];
     			q[++cnt].k=R+1;q[cnt].num=i-m;q[cnt].x=tot;q[cnt].y=last;
     			last=tot;
     		}
     	}
     	else{
     		scanf("%d%d%d",&x,&a,&b);
     		q[++cnt].k=x;q[cnt].num=++nu;q[cnt].x=id[a];q[cnt].y=id[b];
     	}
    }
    sort(q+1,q+cnt+1);
    for(int i=1,j=1;i<=n;i++){
        for(;j<=cnt&&q[j].k==i;j++){
     		if(q[j].num<=0){
     			cut(q[j].x);
     			link(q[j].x,q[j].y);
     		}
     		else ans[q[j].num]=lca(q[j].x,q[j].y);
     	}
    }
    for(int i=1;i<=nu;i++)printf("%d
",ans[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/Xu-daxia/p/10113125.html