zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    神秘题目4
    神秘题目3
    神秘题目2
    AC自动机
    Fence Obstacle Course 题解
    Fractal Streets
    龟速乘
    快速幂
    Stall Reservation
    Sunscreen
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/11534098.html
Copyright © 2011-2022 走看看