Luogu3348 [ZJOI2016]大森林

Link

Solution

首先不能看错题。题目里有句话:子节点的标号为上一个 0 号操作叶子标号加 1

它是站在全局0号操作的角度的,比如上一次在\([l_0,r_0]\)长了一个编号为\(i\)的节点,这一次0操作是在\([l,r]\),那么长出的节点应该是\(i+1\)。这样意味着某个编号的节点只有它那一轮操作的\([l,r]\)范围内的树有。也就是说每棵树终态节点编号是不一定连续的。

而我把它理解成了这棵树中上一次0号操作的标号......(这种错误理解下每棵树终态节点编号都是连续的)。而且样例居然刚好契合这种理解!吐了

正确理解了题意后,就会好做一些。有几个性质:

  • 因为编号为\(i\)的节点只在第\(i-1\)轮0号操作出现,而且保证询问是合法的(询问树上有的节点)。所以可以把每次操作的\([l,r]\)扩展成\([1,n]\)
  • 可以发现1号操作换生长节点并不会影响树的结构。也就是说每个0号操作只受时间在它之前的1号操作的影响。它一定接在它前面离它最近的在该树生效的,1号操作的生长节点下面。

维护每棵树的形态显然是不行的。考虑从左往右遍历第1棵到第n棵树,发现两棵树之间的变化实际上就是插入、删除一些0,1号操作。所以可以维护两棵树之间的变化,即已经有第\(i-1\)棵树,想办法把它变成第\(i\)棵树。

所以现在要考虑的问题是快速修改连边情况,可以用LCT+虚点来维护。

先构造图:

 1.对全局的每个1操作按时间顺序建立虚点。每个虚点连向前一个虚点。第一个虚点连向1号实点,1号实点是定根。

 2.把0操作生成的实点连向时间上离它最近的那个1操作虚点(每个实点看作全局生成,不再是[l,r])。

对于每个1操作,如果它在当前节点生效,那就把它连向对应的生长节点;如果失效,就连向前一个虚点。

这样的话更换生长节点,修改一下虚点的连边就可以转移所有实点的连边。

因为不能每次都遍历所有1操作看是否生效,所以可以考虑差分,让其在\(l\)生效,在\(r+1\)失效。

还有一个问题是求两点之间路径长度不能在\(split\)出来了,因为这样需要\(makeroot\),但是维护的是有根树,直接\(makeroot\)会破坏原树结构。

还是考虑差分。答案是\(dis[a]+dis[b]-2\times dis[lca(a,b)]\)

\(dis\)是到根节点路径,可以\(access,splay\)来求。

\(lca\)同样可以在\(LCT\)上求出!考虑先\(access(a)\),那么\(a\)到根的路径已经在一棵\(splay\)中了。这时再\(access(b)\),那么到达根所在的\(splay\)时经过的那条虚边,指向的父节点,就是\(lca(a,b)\)

inline int access(int x){
        int y=0;
        for(;x;y=x,x=fa[x])
            splay(x),ch[x][1]=y,pushup(x);
        return y;
    }

\(access(a)\)之后再\(access(b)\),最后的那个\(y\)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define REP(i,a,b) for(int i=(a),ed=(b);i<=ed;++i)
inline int read(){
    register int x=0,f=1;register char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
    return f?x:-x;
}

const int M=2e5+10,_=M<<1;
int n,m,rp,vp,L[M],R[M],cnt,ans[M],ans_cnt;
struct Squery{int type,p,x,y,id;inline bool operator<(const Squery &a)const{return p==a.p?type<a.type:p<a.p;}}q[_];

namespace LCT{
    int fa[_],ch[_][2],s[_],v[_];
    inline void pushup(int x){s[x]=s[ch[x][0]]+s[ch[x][1]]+v[x];}
    inline int getdir(int x){return x==ch[fa[x]][1];}
    inline bool noroot(int x){return x==ch[fa[x]][0]||x==ch[fa[x]][1];}
    inline void rotate(int x){
        int f=fa[x],p=fa[f],k=getdir(x),s=ch[x][!k];
        ch[x][!k]=f;ch[f][k]=s;if(noroot(f))ch[p][getdir(f)]=x;
        fa[f]=x;fa[x]=p;if(s)fa[s]=f;
        pushup(f);
    }
    inline void splay(int x){
        for(int f=fa[x];noroot(x);rotate(x),f=fa[x])
            if(noroot(f))rotate(getdir(f)==getdir(x)?f:x);
        pushup(x);
    }
    inline int access(int x){
        int y=0;
        for(;x;y=x,x=fa[x])
            splay(x),ch[x][1]=y,pushup(x);
        return y;
    }
    inline void cut(int x){
        access(x);splay(x);
        ch[x][0]=fa[ch[x][0]]=0;
        pushup(x);
    }
    inline void link(int x,int y){
        access(x);splay(x);fa[x]=y;
    }
}using namespace LCT;

int main(){
    n=read(),m=read();vp=m;
    L[1]=1,R[1]=n;
    link(++vp,++rp);
    REP(t,1,m){
        int op=read();
        if(!op){
            ++rp;L[rp]=read(),R[rp]=read();v[rp]=1;
            link(rp,vp);
        }
        else if(op==1){
            int l=read(),r=read(),x=read();
            l=max(l,L[x]),r=min(r,R[x]);
            if(l>r)continue;
            ++vp;link(vp,vp-1);
            q[++cnt]=(Squery){0,l,vp,x,0};
            q[++cnt]=(Squery){0,r+1,vp,vp-1,0};
        }
        else{
            int x=read(),u=read(),v=read();
            q[++cnt]=(Squery){1,x,u,v,++ans_cnt};
        }
    }
    sort(q+1,q+cnt+1);
    REP(t,1,cnt){
        if(q[t].type){
            int res=0,x=q[t].x,y=q[t].y,p;
            access(x);splay(x);res+=s[x];
            p=access(y);splay(y);res+=s[y];
            access(p);splay(p);res-=s[p]*2;
            ans[q[t].id]=res;
        }
        else{
            cut(q[t].x);link(q[t].x,q[t].y);
        }
    }
    REP(i,1,ans_cnt)printf("%d\n",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/fruitea/p/12144834.html
今日推荐