[ZJOI2016]大森林

Description:

小Y家里有一个大森林,里面有n棵树,编号从1到n

0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例如,第一个 0 号操作产生的子节点标号为 2), l 到 r 之间的树长出的节点标号都相同。保证 1<=l<=r<=n 。

1 l r x 表示将第 l 棵树到第 r 棵树的生长节点改到标号为 x 的节点。对于 i (l<=i<=r)这棵树,如果标号 x的点不在其中,那么这个操作对该树不产生影响。保证 1<=l<=r<=n , x 不超过当前所有树中节点最大的标号。

2 x u v 询问第 x 棵树中节点 u 到节点 v 点的距离,也就是在第 x 棵树中从节点 u 和节点 v 的最短路上边的数量。保证1<=x<=n,这棵树中节点 u 和节点 v 存在。

Hint:

$ N<=105,M<=2*105 $

Solution:

一开始想到线段树套LCT,然而标记无法下传
考虑朴素做法,每个节点用LCT维护一片森林,空间显然无法承受
这时我们需要转换思路
因为题目没有强制在线,所以我们可以只维护一颗”树“,然后按顺序处理1、2操作
每次1操作直接把该生长节点的子树”嫁接“到另一个生长节点
为了保证复杂度,我们需要在生长节点处开一个虚点,这样就不需要多点换父亲了
2操作直接用求个LCA就行了

#include<bits/stdc++.h>
using namespace std;
const int mxn=3e5+5;
struct Q {
    int pos,id,x,y,qr;
}q[mxn];
int n,m,p,s,tot,cnt,sum;
int t[mxn],lp[mxn],rp[mxn],bl[mxn],fa[mxn],ch[mxn][2],ans[mxn],val[mxn];

int cmp(Q x,Q y) {
    return x.pos==y.pos?x.id<y.id:x.pos<y.pos;
}

namespace lct {
    int isnotrt(int x) {
        return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
    }
    void push_up(int x) {
        t[x]=t[ch[x][0]]+t[ch[x][1]]+val[x];
    }
    void rotate(int x) {
        int y=fa[x],z=fa[y],tp=ch[y][1]==x;
        if(isnotrt(y)) ch[z][ch[z][1]==y]=x; fa[x]=z;
        ch[y][tp]=ch[x][tp^1]; fa[ch[x][tp^1]]=y;
        ch[x][tp^1]=y; fa[y]=x;
        push_up(y),push_up(x);
    }
    void splay(int x) {
        while(isnotrt(x)) {
            int y=fa[x],z=fa[y];
            if(isnotrt(y)) 
                (ch[y][1]==x)^(ch[z][1]==y)?rotate(x):rotate(y);
            rotate(x);	
        }
    }
    int access(int x) {
        int y;
        for(y=0;x;x=fa[y=x])  
            splay(x),ch[x][1]=y,push_up(x);
        return y;	
    }
    void link(int x,int y) {
        splay(x); fa[x]=y;
    }
    void cut(int x) {
        access(x); splay(x);  
        ch[x][0]=fa[ch[x][0]]=0; 
        push_up(x);
    }
}
using namespace lct;

int main()
{
    scanf("%d%d",&n,&m); int res,lca,opt,l,r,x,y; 
    p=tot=val[1]=t[1]=lp[1]=bl[1]=1; rp[1]=n; int now=2; link(++p,1);
    for(int i=1;i<=m;++i) {
        scanf("%d",&opt);
        if(opt==0) {
            scanf("%d%d",&l,&r);
            bl[++tot]=++p; link(p,now); //每次把新节点直接长在最近更新的生成节点,考虑这样为什么不会错,因为实点之间的虚点不会影响答案
            lp[tot]=l,rp[tot]=r; 
            val[p]=t[p]=1;
        }
        else if(opt==1) {
            scanf("%d%d%d",&l,&r,&x); 
            l=max(l,lp[x]); r=min(r,rp[x]); 
            if(l>r) continue;//去掉无用的区间
            link(++p,now);
            q[++s]=(Q){l,i,p,bl[x],0};
            q[++s]=(Q){r+1,i,p,now,0};
            now=p;
        }
        else scanf("%d%d%d",&l,&x,&y),q[++s]=(Q){l,i,bl[x],bl[y],++sum};
    }
    sort(q+1,q+s+1,cmp); 
    for(int i=1;i<=s;++i) {
        if(q[i].qr>0) {
            access(q[i].x); splay(q[i].x); res=t[q[i].x]; //因为这题规定根为1,所以我们不能makert
            lca=access(q[i].y); splay(q[i].y); res+=t[q[i].y];
            access(lca); ans[q[i].ss]=res-t[lca]*2; 
        }
        else cut(q[i].x),link(q[i].x,q[i].y);
    }
    for(int i=1;i<=sum;++i) printf("%d
",ans[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/list1/p/10421740.html