[HNOI2016]树

https://www.luogu.org/problemnew/show/P3248

题解

模拟题意即可,把每次接过去的子树看做一个点,然后这个关系构成了一棵树。

大力倍增即可。

代码

#include<bits/stdc++.h>
#define N 100009
#define ls tr[cnt].l
#define rs tr[cnt].r
using namespace std;
typedef long long ll;
int p[20][N],deep[N],tot,head[N],n,m,q,tott,dep[N],pr[20][N],T[N],size[N],c[N],pre[N];
ll dis[20][N],noww,b[N],nowid;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct node{
    int l,r,size;
}tr[N*40];
struct edge{int n,to;}e[N<<1];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
inline void ins(int &cnt,int l,int r,int id){
    cnt=++tott;
    tr[cnt].size++;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(mid>=id)ins(ls,l,mid,id);
    else ins(rs,mid+1,r,id);
}
int merge(int u,int v){
    if(!u||!v)return u^v;
    int cnt=++tott;
    tr[cnt].size=tr[u].size+tr[v].size;
    ls=merge(tr[u].l,tr[v].l);
    rs=merge(tr[u].r,tr[v].r);
    return cnt;
} 
inline int kth(int &cnt,int l,int r,int k){
    int mid=(l+r)>>1;
    if(l==r)return l;
    if(tr[ls].size<k)return kth(rs,mid+1,r,k-tr[ls].size);
    else return kth(ls,l,mid,k);
}
void dfs(int u,int fa){
    ins(T[u],1,n,u);
    size[u]=1;
    for(int i=1;(1<<i)<=deep[u];++i)p[i][u]=p[i-1][p[i-1][u]];
    for(int i=head[u];i;i=e[i].n)if(e[i].to!=fa){
        int v=e[i].to;deep[v]=deep[u]+1;p[0][v]=u;
        dfs(v,u);
        size[u]+=size[v];
        T[u]=merge(T[u],T[v]); 
    }
}
inline int getlca(int u,int v){
    if(deep[u]<deep[v])swap(u,v);
    for(int i=19;i>=0;--i)if(deep[u]-(1<<i)>=deep[v])u=p[i][u];
    if(u==v)return u;
    for(int i=19;i>=0;--i)if(p[i][u]!=p[i][v])u=p[i][u],v=p[i][v];
    return p[0][u];
}
inline int jump(int x,int xx){
    for(int i=19;i>=0;--i)if((1<<i)&xx)x=pr[i][x];
    return x;
}
int main(){
    n=rd();m=rd();q=rd();
    ll u,v;
    for(int i=1;i<n;++i){
        u=rd();v=rd();
        add(u,v);add(v,u);
    }
    dfs(1,0);
    b[++b[0]]=1;noww=n;
    c[b[0]]=1;
    nowid=1;
    while(m--){
        u=rd();v=rd();
        b[++b[0]]=noww+1;noww+=size[u];
        c[b[0]]=u;
        int x=upper_bound(b+1,b+b[0]+1,v)-b-1;
        int preid=x,preroot=c[x],now=kth(T[preroot],1,n,v-b[x]+1);
        //preid 被接的树的编号 preroot 被接的树的根 now被接的节点编号 
    //  cout<<x<<" "<<preroot<<" "<<now<<" "<<preid<<" biu \n"; 
        ++nowid;
        pr[0][nowid]=preid;dis[0][nowid]=deep[now]-deep[preroot]+1;dep[nowid]=dep[preid]+1;
        pre[nowid]=now;
        for(int i=1;(1<<i)<=dep[nowid];++i)pr[i][nowid]=pr[i-1][pr[i-1][nowid]],
        dis[i][nowid]=dis[i-1][nowid]+dis[i-1][pr[i-1][nowid]];
    }
    while(q--){
        u=rd();v=rd();
        int x=upper_bound(b+1,b+b[0]+1,u)-b-1,y=upper_bound(b+1,b+b[0]+1,v)-b-1;
        int now1=kth(T[c[x]],1,n,u-b[x]+1),now2=kth(T[c[y]],1,n,v-b[y]+1);
//      cout<<x<<" "<<y<<"  "<<now1<<" "<<now2<<endl;       
        ll ans=0;
        if(x==y){
            ans=deep[now1]+deep[now2]-2*deep[getlca(now1,now2)];
            printf("%lld\n",ans);
            continue;
        }
        if(dep[x]<dep[y])swap(x,y),swap(now1,now2);
        int bu=dep[x]-dep[y];
        if(jump(x,bu)==y){
          int xx=bu-1;ans+=deep[now1]-deep[c[x]];         
          for(int i=19;i>=0;--i)if((1<<i)&xx){
              ans+=dis[i][x];x=pr[i][x];
          }
          ans++;
          ans+=deep[now2]+deep[pre[x]]-2*deep[getlca(now2,pre[x])];
        }
        else{
            ans+=deep[now1]-deep[c[x]]+deep[now2]-deep[c[y]];
            for(int i=19;i>=0;--i)if(dep[x]-(1<<i)>=dep[y]){
              ans+=dis[i][x];x=pr[i][x];
             }
            for(int i=19;i>=0;--i)if(pr[i][x]!=pr[i][y]){
                ans+=dis[i][x]+dis[i][y];x=pr[i][x];y=pr[i][y];
            }
            int dx=pre[x],dy=pre[y];ans+=2;
        //  cout<<x<<" "<<y<<" "<<pr[0][x]<<" "<<pr[0][y]<<"  ";
            ans+=deep[dx]+deep[dy]-2*deep[getlca(dx,dy)];
        }
        printf("%lld\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/ZH-comld/p/10829898.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值