ZJOI 4573: [Zjoi2016]大森林

神仙题,不愧是噩梦的ZJOI。

首先我们发现操作不会改变树的形态,因此可以离线,我们最后从\(1\)\(n\)枚举每棵树,考虑两棵树之间的差异并修改

然后这里维护树的时候涉及了许多变化,因此要用LCT来维护(PS:注意这里的LCT不能换根,因为树是有根的)

考虑对于增加节点的操作,不难发现对于原来长在\([l,r]\)的节点就算让\([1,n]\)中都长了也不会有影响,因为其它的树里就算长了也是多余的

但是这样就好了???注意到修改生长节点时我们不能修改没有这个点的树,因此还要记一下这个点实际存在的区间

然后我们考虑询问,原来查询树上路径的长度时不难的,可是现在的LCT不能split(因为不能makeroot),怎么求两点间距离呢?

考虑树上差分,两点\((x,y)\)间距离为\(dep_x+dep_y-2\times dep_{\operatorname {LCA} (x,y)}\)

那么现在我们就要搞出\(dep\)和求出\(\operatorname{LCA}\)

很显然我们考虑记录每个点子树内点的个数,然后在accesssplay之后以这个点为根的Splay维护的就是根节点到这个点路径上的信息,其实就等价于深度

然后关于LCA是一个常用的技巧,考虑求\(\operatorname{LCA}(x,y)\),我们先后access(x)access(y),然后不难发现此时\(\operatorname{LCA}(x,y)\)就是access(y)时最后操作的节点,具体实现可以看代码

然后考虑修改,这个操作相当于要把一个点的子树全部嫁接到另一个点上

据说这个操作可以直接用ETT完成,但显然我是不会这种黑科技的

那么我们考虑建立虚点,把这个点的所有儿子先往虚点上连,然后要嫁接的时候直接对虚点link,cut即可

PS:显然虚点不能加入\(dep\)的计算,因此要把点权赋为\(0\)

然后就口胡完了,具体的细节参考代码

#include
#include
#include
#define RI register int
#define CI const int&
#define Tp template 
using namespace std;
const int N=200005;
struct event
{
    int pos,t,x,y;
    inline event(CI P=0,CI T=0,CI X=0,CI Y=0)
    {
        pos=P; t=T; x=X; y=Y;
    }
    inline friend bool operator < (const event& A,const event& B)
    {
        return A.posl?L[x]:l; r=R[x]r) continue;
                LCT.link(++cnt_all,lst); et[++cnt_et]=event(l,i-m,cnt_all,id[x]);
                et[++cnt_et]=event(r+1,i-m,cnt_all,lst); lst=cnt_all; break;
            case 2:
                F.read(x); et[++cnt_et]=event(l,++cnt_q,id[r],id[x]); break;
        }
    }
    for (sort(et+1,et+cnt_et+1),i=1;i<=cnt_et;++i)
    {
        if (et[i].t>0) ans[et[i].t]=LCT.query(et[i].x,et[i].y);
        else LCT.cut(et[i].x),LCT.link(et[i].x,et[i].y);
    }
    for (i=1;i<=cnt_q;++i) F.write(ans[i]); return F.Fend(),0;
}

你可能感兴趣的:(ZJOI 4573: [Zjoi2016]大森林)