P4271 [USACO18FEB]New Barns

传送门

动态维护树上节点到其他所有点的最长距离

算是 $LCT$ 的模板之一吧

$LCT$ 维护直径,这一题其实可以不用维护直径的,但是我当模板写了

首先我们都知道 $LCT$ 里面的 $splay$ 维护的是一段树链,$splay$ 的子树内的节点恰好为原树上一段连续的链

对每条实链的 $splay$ 维护 $lmx[x],rmx[x],mxs[x],sum[x]$ ,分别表示节点 $x$ 的 splay子树内:

原树上对应的链上深度最浅的节点 $y$ 到原树上该节点(y)子树的最长路径长度 $lmx$

原树上对应的链上深度最深的节点 $y$ 到原树上该节点(y)子树的最长路径长度 $rmx$

原树上该节点 $x$ 子树内的直径 $mxs$

原树上对应的链的节点的权值之和 $sum$

同时我们还要对每个节点维护两个可删堆或者 $multiset$,$H[x],P[x]$ ,分别存该节点虚儿子往下走的最长路径,该节点虚儿子子树内的最长路径

然后我们就可以写出 $splay$ 更新节点的函数:

inline void pushup(int x)
    {
        int &lc=c[x][0],&rc=c[x][1];
        sum[x]=sum[lc]+sum[rc]+val[x];
        ll t=max(0ll,H[x].fir()), L=max(t,rmx[lc])+val[x], R=max(t,lmx[rc])+val[x];
        lmx[x]=max(lmx[lc], sum[lc]+R ); rmx[x]=max(rmx[rc], sum[rc]+L );
        mxs[x]=max( max( rmx[lc]+R , lmx[rc]+L ) , max(mxs[lc],mxs[rc]) );
        mxs[x]=max(mxs[x],P[x].fir()); mxs[x]=max(mxs[x], t+val[x] );
        mxs[x]=max(mxs[x], t+H[x].sec()+val[x] );
    }

看一眼感觉眼睛花了,不急,慢慢分析

首先 $lc,rc$ 的定义,$sum$ 的维护十分显然

然后定义 $t$ 表示虚儿子贡献的最长路径,注意我们的模板要考虑到节点权值可能为负数的情况,虚儿子可能路径都是负的,所以要和 $0$ 取 $max$

定义 $L$ 表示节点 $x$ 在原树上(包括 $x$)往上不超过实链顶部的原树子树内的最长路径(这里往上包括往 $x$ 的虚儿子)

定义 $R$ 表示节点 $x$ 在原树上(包括 $x$)往下整个原树子树内的最长路径(这里往下也包括往 $x$ 的虚儿子)

那么首先 $lmx[x]$ 可以由 $lc$ 贡献过来,即链不经过 $x$,当然也要考虑经过 $x$,所以还要和 $sum[lc]+R$ 取 $max$

$sum[lc]+R$ 意思就是往上的整条实链和 $x$ 往下最长的链接在一起

同样的,$rmx[x]$ 也是一个道理,可以由 $rc$ 贡献(不经过 $x$) 也可以经过 $x$,经过 $x$ 的最长路程显然 $sum[rc]+L$

然后是恶心的 $mxs$,一大波分类讨论,首先 $mxs$ 也可以由左右儿子更新,然后分别考虑强制经过 $x$ 往上的实链和 $x$ 的往下的实链时的最长路径

再然后就是虚儿子子树了,首先虚儿子子树的路径显然可以由 $P[x]$ 贡献,当然也可以以 $x$ 为起点通向虚儿子的链

最后再考虑一波从 $x$ 的一个虚儿子经过 $x$ 再走向 $x$ 的另一个虚儿子

然后我们就讨论完了

剩下的就是维护 $H$ 和 $P$ 了, $access$ 和 $link$ 的时候注意更新的细节即可

对于 $u$ 的虚儿子 $v$ ,$H[u].push(lmx[v])$ ,即把虚儿子实链上深度最小的节点(就是 $v$ 自己)到整个原树子树的最大路程贡献给 $u$

$P[u].push(mxs[v])$ ,十分显然

其他就是普普通通的 $LCT$,下传翻转标记的时候记得交换 $lmx,rmx$ 即可

手贱写的是可删堆,不开 $O2$ $20$ 到 $30$ 分,开 $O2$ 快 $5$ 倍直接过了是什么操作

自己测用 $set$ 不开 $O2$ ,$50$ 到 $100$ (看常数优化),但是开 $O2$ 却没可删堆开 $O2$ 快是怎么回事??

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=4e5+7;
const ll INF=1e18;
namespace LCT{
    int c[N][2],fa[N],sz[N],val[N];
    ll sum[N],rmx[N],lmx[N],mxs[N];
    bool rev[N];
    struct Eraseable_Heap {//可删堆
        priority_queue <ll> hp,rub;
        inline void ins(ll v) { hp.push(v); }
        inline void del(ll v) { hp.top()==v ? hp.pop() : rub.push(v); }
        inline ll fir()//求最大值
        {
            while(!hp.empty()&&!rub.empty()&&hp.top()==rub.top()) hp.pop(),rub.pop();
            return hp.empty() ? -INF : hp.top();//注意判堆空的情况
        }
        inline ll sec()//求次大值
        {
            ll x=fir(); if(x==-INF) return -INF;//注意判堆空的情况
            hp.pop(); ll res=fir(); hp.push(x);
            return res;
        }
    }H[N],P[N];
    inline void ins(int u,int v) { H[u].ins(lmx[v]); P[u].ins(mxs[v]); }//节点u加入虚儿子v
    inline void del(int u,int v) { H[u].del(lmx[v]); P[u].del(mxs[v]); }//节点u删除虚儿子v
    inline void pushup(int x)//核心的更新函数
    {
        int &lc=c[x][0],&rc=c[x][1];
        sum[x]=sum[lc]+sum[rc]+val[x];
        ll t=max(0ll,H[x].fir()), L=max(t,rmx[lc])+val[x], R=max(t,lmx[rc])+val[x];
        lmx[x]=max(lmx[lc], sum[lc]+R ); rmx[x]=max(rmx[rc], sum[rc]+L );
        mxs[x]=max( max( rmx[lc]+R , lmx[rc]+L ) , max(mxs[lc],mxs[rc]) );
        mxs[x]=max(mxs[x],P[x].fir()); mxs[x]=max(mxs[x], t+val[x] );
        mxs[x]=max(mxs[x], t+H[x].sec()+val[x] );
    }
    inline void pushdown(int x)
    {
        if(!x||!rev[x]) return;
        int &lc=c[x][0],&rc=c[x][1]; rev[x]=0;
        swap(lc,rc); swap(lmx[x],rmx[x]);
        if(lc) rev[lc]^=1;
        if(rc) rev[rc]^=1;
    }
    inline void rever(int x) { rev[x]=1; pushdown(x); }
    inline bool noroot(int x) { return (c[fa[x]][0]==x)|(c[fa[x]][1]==x); }
    inline void rotate(int x)
    {
        int y=fa[x],z=fa[y],d=(c[y][1]==x);
        if(noroot(y)) c[z][c[z][1]==y]=x;
        fa[x]=z; fa[y]=x; fa[c[x][d^1]]=y;
        c[y][d]=c[x][d^1]; c[x][d^1]=y;
        pushup(y); pushup(x);
    }
    void push_rev(int x)
    {
        if(noroot(x)) push_rev(fa[x]);
        else pushdown(x);
        pushdown(c[x][0]); pushdown(c[x][1]);
    }
    inline void splay(int x)
    {
        push_rev(x);
        while(noroot(x))
        {
            int y=fa[x],z=fa[y];
            if(noroot(y))
                (c[y][0]==x ^ c[z][0]==y) ? rotate(x) : rotate(y);
            rotate(x);
        }
    }
    inline void access(int x)
    {
        for(int y=0;x;y=x,x=fa[x])
        {
            splay(x);/*注意要先splay(x),不然ins(x,y)以后x在splay上的父亲都要更新*/ if(y) del(x,y);//注意特判y是否为0
            if(c[x][1]) ins(x,c[x][1]); //注意特判y是否为0
            c[x][1]=y,pushup(x); 
        }
    }
    inline void makeroot(int x) { access(x); splay(x); rever(x); }
    inline int findroot(int x)
    {
        access(x); splay(x); pushdown(x);
        while(c[x][0]) pushdown(c[x][0]),x=c[x][0];
        splay(x); return x;
    }
    inline void split(int x,int y) { makeroot(x); access(y); splay(y); }
    inline void link(int x,int y)//这里的link有点不一样
    {
        makeroot(x); if(findroot(y)==x) return;
        makeroot(y); fa[x]=y; ins(y,x); pushup(y);//记得pushup
        //这里要makeroot(y)不然更新y的时候y在原树往上的整条链的所有实链都要更新
    }
    inline void cut(int x,int y)//cut因为断的是实链所以不要更新H和P
    {
        makeroot(x);
        if(findroot(y)!=x||fa[y]!=x||c[y][0]) return;
        fa[y]=c[x][1]=0; pushup(x);
    }
}
int n,cnt;
inline ll query(int x) { LCT::access(x); LCT::splay(x); return LCT::rmx[x]; }//此时x的实链上最深的点就是x
int main()
{
    n=read(); char s[7]; int a;
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s); a=read();
        if(s[0]=='Q') { printf("%lld
",query(a)-1); continue; }
        cnt++; LCT::val[cnt]=1; if(a!=-1) LCT::link(a,cnt);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/11534098.html