[BZOJ4539][HNOI2016]树-虚树

Description

  小A想做一棵很大的树,但是他手上的材料有限,只好用点小技巧了。开始,小A只有一棵结点数为N的树,结点的编号为1,2,…,N,其中结点1为根;我们称这颗树为模板树。小A决定通过这棵模板树来构建一颗大树。构建过程如下:
  
(1)将模板树复制为初始的大树。
(2)以下(2.1)(2.2)(2.3)步循环执行M次
(2.1)选择两个数字a,b,其中1<=a<=N,1<=b<=当前大树的结点数。
(2.2)将模板树中以结点a为根的子树复制一遍,挂到大树中结点b的下方(也就是说,模板树中的结点a为根的子树复制到大树中后,将成为大树中结点b的子树)。
(2.3)将新加入大树的结点按照在模板树中编号的顺序重新编号。例如,假设在进行2.2步之前大树有L个结点,模板树中以a为根的子树共有C个结点,那么新加入模板树的C个结点在大树中的编号将是L+1,L+2,…,L+C;大树中这C个结点编号的大小顺序和模板树中对应的C个结点的大小顺序是一致的。

下面给出一个实例。假设模板树如下图:
1
根据第(1)步,初始的大树与模板树是相同的。在(2.1)步,假设选择了a=4,b=3。运行(2.2)和(2.3)后,得到新的大树如下图所示
2
现在他想问你,树中一些结点对的距离是多少。

Input

  第一行三个整数:N,M,Q,以空格隔开,N表示模板树结点数,M表示第(2)中的循环操作的次数,Q 表示询问数量。
  接下来N-1行,每行两个整数 fr,to,表示模板树中的一条树边。再接下来M行,每行两个整数x,to,表示将模板树中 x 为根的子树复制到大树中成为结点to的子树的一次操作。
  再接下来Q行,每行两个整数fr,to,表示询问大树中结点 fr和 to之间的距离是多少。N,M,Q<=100000

Output

  输出Q行,每行一个整数,第 i行是第 i个询问的答案。

Sample Input

5 2 3
1 4
1 3
4 2
4 5
4 3
3 2
6 9
1 8
5 3

Sample Output

6
3
3

HINT

经过两次操作后,大树变成了下图所示的形状:

3
结点6到9之间经过了6条边,所以距离为6;类似地,结点1到8之间经过了3条边;结点5到3之间也经过了3条边。


HNOI居然还有这种码农题……
突然感觉相比之下ZJOI的题质量真高……


思路:
这不是钦定了要利用模板树不变的性质嘛……

对于每一次复制,考虑只建立其中的一个节点来代表这次复制。可以认为这是类似虚树的思路。
为了方便计算,令代表节点为这次复制的子树的根节点。
于是,对于每次复制的代表节点,其父亲为它在展开树上的父亲所在的代表节点,边权为它在展开树上的父亲到其父亲的代表节点的距离+1。

然后就是求距离了。

对于询问的两个点 a b,首先找到其对应的代表节点 rta rtb ,这个可以用一个 vector upper_bound() 实现。

为了方便描述,假设 dep[rta]<dep[rtb]
考虑一种特殊情况: lca(rta,rtb)==rta || lca(rta,rtb)==rtb
此时,找到 rta rtb 方向上的儿子 rtson ,答案为 dis(b,rtb)+dis(rtb,rtson)+1+dis(fa[rtson],a)

对于其余情况,令 rtlca=lca(rta,rtb) ,同时令 rtsona rtlca rta 方向上的儿子, rtsonb 同理。
可以得到答案为:
dis(a,rta)+dis(rta,rtsona)+1+dis(fa[rtsona],fa[rtsonb])+1+dis(rtsonb,rtb)+dis(rtb,b)

剩下的都™是细节~

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=100009;
const int K=23;
int n,m,q;

inline ll read()
{
    ll x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

namespace tmpt
{
    int to[N<<1],nxt[N<<1],beg[N],tot;
    int dep[N],fa[N][K],id[N],ed[N],rt[N],dfn;
    ll dis[N];

    namespace pst
    {
        const int M=N*20;
        int t[M],ls[M],rs[M],tot;

        inline int insert(int pre,int l,int r,int p)
        {
            int now=++tot;t[now]=t[pre]+1;
            if(l==r)return now;int mid=l+r>>1;
            if(p<=mid)ls[now]=insert(ls[pre],l,mid,p),rs[now]=rs[pre];
            else rs[now]=insert(rs[pre],mid+1,r,p),ls[now]=ls[pre];
            return now;
        }

        inline int query(int tl,int tr,int l,int r,int k)
        {
            if(l==r)return l;int mid=l+r>>1;
            if(k<=t[ls[tr]]-t[ls[tl]])
                return query(ls[tl],ls[tr],l,mid,k);
            else
                return query(rs[tl],rs[tr],mid+1,r,k-(t[ls[tr]]-t[ls[tl]]));
        }
    }

    inline int query(int x,int k)
    {
        return pst::query(rt[id[x]-1],rt[ed[x]],1,n,k);
    }

    inline int siz(int x){return ed[x]-id[x]+1;}

    inline void add(int u,int v)
    {
        to[++tot]=v;
        nxt[tot]=beg[u];
        beg[u]=tot;
    }

    inline void dfs(int u)
    {
        id[u]=++dfn;
        rt[dfn]=pst::insert(rt[dfn-1],1,n,u);
        if(u==1)dep[u]=1;
        for(int i=beg[u];i;i=nxt[i])
            if(to[i]!=fa[u][0])
            {
                fa[to[i]][0]=u;
                for(int j=1;j<K;j++)
                    fa[to[i]][j]=fa[fa[to[i]][j-1]][j-1];
                dep[to[i]]=dep[u]+1;
                dfs(to[i]);
            }
        ed[u]=dfn;
    }

    inline int lca(int a,int b)
    {
        if(dep[a]>dep[b])swap(a,b);
        for(int i=K-1;i>=0;i--)
            if(dep[fa[b][i]]>=dep[a])
                b=fa[b][i];
        if(a==b)return a;
        for(int i=K-1;i>=0;i--)
            if(fa[a][i]!=fa[b][i])
                a=fa[a][i],b=fa[b][i];
        return fa[a][0];
    }

    inline ll dist(int a,int b)
    {
        return dep[a]+dep[b]-2ll*dep[lca(a,b)];
    }
}

namespace bigt
{
    int ori[N],stkid[N],top;
    int fa[N][K],dep[N],tot;
    ll stk[N],posid[N],cfa[N],dis[N],curid;

    inline void buildfa(int a,int fat)
    {
        fa[a][0]=fat;
        for(int i=1;i<K;i++)
            fa[a][i]=fa[fa[a][i-1]][i-1];
    }

    inline int lca(int a,int b)
    {
        if(dep[a]>dep[b])swap(a,b);
        for(int i=K-1;i>=0;i--)
            if(dep[fa[b][i]]>=dep[a])
                b=fa[b][i];
        if(a==b)return a;
        for(int i=K-1;i>=0;i--)
            if(fa[a][i]!=fa[b][i])
                a=fa[a][i],b=fa[b][i];
        return fa[a][0];
    }

    inline int jump(int a,int b)
    {
        for(int i=K-1;~i;i--)
            if(b>>i&1)
                a=fa[a][i];
        return a;
    }

    inline ll dist(int a,int b)
    {
        return dis[a]+dis[b]-2ll*dis[lca(a,b)];
    }

    inline void link(int u,int fat,ll w)
    {
        buildfa(u,fat);
        dep[u]=dep[fat]+1;
        dis[u]=dis[fat]+w;
    }

    inline ll findrt(ll x)
    {
        return stkid[upper_bound(stk+1,stk+top+1,x)-stk-1];
    }

    inline int orip(ll x,int rt)
    {
        return tmpt::query(ori[rt],x-posid[rt]+1);
    }

    inline void modify(int u,ll fat)
    {
        int now=++tot;
        stk[++top]=curid+1;
        posid[now]=curid+1;
        stkid[top]=now;
        ori[now]=u;
        curid+=tmpt::siz(u);

        if(fat)
        {
            int rt=findrt(fat);
            ll w=tmpt::dist(ori[rt],orip(fat,rt));
            link(now,rt,w+1ll);
            cfa[now]=fat;
        }
        else dep[now]=1ll;
    }

    inline ll fadist(int x,int rt)
    {
        return tmpt::dep[x]-tmpt::dep[ori[rt]];
    }

    inline ll query(ll a,ll b)
    {
        int rta=findrt(a),rtb=findrt(b);
        if(dep[rta]>dep[rtb])
            swap(rta,rtb),swap(a,b);
        int pa=orip(a,rta),pb=orip(b,rtb);

        if(rta==rtb)
            return tmpt::dist(pa,pb);
        int rtlca=lca(rta,rtb);
        if(rtlca==rta)
        {
            int rtson=jump(rtb,dep[rtb]-dep[rta]-1);
            int fason=orip(cfa[rtson],rta);
            return fadist(pb,rtb)+dist(rtb,rtson)+1+tmpt::dist(fason,pa); 
        }
        int rtsona=jump(rta,dep[rta]-dep[rtlca]-1);
        int rtsonb=jump(rtb,dep[rtb]-dep[rtlca]-1);
        int fasona=orip(cfa[rtsona],rtlca);
        int fasonb=orip(cfa[rtsonb],rtlca);
        return dis[rta]-dis[rtsona]+1+dis[rtb]-dis[rtsonb]+1+tmpt::dist(fasona,fasonb)+fadist(pa,rta)+fadist(pb,rtb);
    }
}

int main()
{
    n=read();m=read();q=read();
    for(int i=1,u,v;i<n;i++)
    {
        u=read();v=read();
        tmpt::add(u,v);
        tmpt::add(v,u);
    }

    tmpt::dfs(1);
    bigt::modify(1,0);
    for(int i=1;i<=m;i++)
    {
        ll x=read();
        bigt::modify(x,read());
    }

    for(int i=1;i<=q;i++)
        printf("%lld\n",bigt::query(read(),read()));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值