bzoj4539: [Hnoi2016]树

18 篇文章 0 订阅
4 篇文章 0 订阅

题目链接

bzoj4539

题目描述

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)步,初始的大树与模板树是相同的。在(2.1)步,假设选择了a=4,b=3。运行(2.2)和(2.3)后,得到新的大树如下图所示
这里写图片描述
现在他想问你,树中一些结点对的距离是多少。

Input

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

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

经过两次操作后,大树变成了下图所示的形状:
这里写图片描述
结点6到9之间经过了6条边,所以距离为6;类似地,结点1到8之间经过了3条边;结点5到3之间也经过了3条边。

题解

我们将每次操作的一颗子树看成一个块,可以得到一颗树。我们可以二分得到一个节点属于哪个块,以及它在块中是排第几,那么可以用主席树维护新树和模板树节点的对应关系。
对于一次询问,如果他们在同一块,则直接在模板树中求lca,否则块之间在新树中倍增,块之内在模板树中求就可以了。


#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=100010;
typedef long long ll;
struct node{
    int ls,rs,d;
}t[N*20];
struct tree{
    struct edge{
        int x,nex,data;
    }e[N*2];
    int f[N][20],dep[N],first[N],size[N],tot;
    ll dis[N];
    int dfn[N],pos[N],ti;

    void add(int x,int y,int d){
        e[++tot].x=y;
        e[tot].data=d;
        e[tot].nex=first[x];
        first[x]=tot;
    }
    void dfs(int x,int y){
        dfn[x]=++ti; pos[ti]=x;
        f[x][0]=y; dep[x]=dep[y]+1;
        for(int i=1;i<=17;i++) f[x][i]=f[f[x][i-1]][i-1];
        for(int i=first[x];i;i=e[i].nex)
        if(e[i].x!=y){
            dis[e[i].x]=dis[x]+e[i].data;
            dfs(e[i].x,x);
            size[x]+=size[e[i].x];
        }
        size[x]++;
    }
    int LCA(int x,int y){
        if(dep[x]<dep[y]) std::swap(x,y);
        for(int i=17;~i;i--)
        if(dep[f[x][i]]>=dep[y]) x=f[x][i];
        if(x==y) return x;
        for(int i=17;~i;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    int s_lca(int x,int y){
        for(int i=17;~i;i--)
        if(dep[f[x][i]]>dep[y]) x=f[x][i];
        return x;
    }
}t1,t2;
int num,n,m,q,tmp1,tmp2,tmp,lca,cnt;
int top[N],st[N],dis[N],rt[N];
ll a[N],s,ans,x,y;

void insert(int &k,int l,int r,int x){
    t[++cnt]=t[k]; k=cnt; t[k].d++;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(x<=mid) insert(t[k].ls,l,mid,x);
    else insert(t[k].rs,mid+1,r,x);
}
int query(int k1,int k2,int l,int r,int x){
    if(l==r) return l;
    int tmp=t[t[k1].ls].d-t[t[k2].ls].d;
    int mid=(l+r)>>1;
    if(tmp>=x) return query(t[k1].ls,t[k2].ls,l,mid,x);
    else return query(t[k1].rs,t[k2].rs,mid+1,r,x-tmp);
}
int Find(ll x){
    int l=1,r=num+1,mid;
    while(r-l>1){
        mid=(l+r)>>1;
        if(a[mid]>x) r=mid;
        else l=mid;
    }
    return l;
}
int Get(ll x,int tmp){
    if(tmp==1) return x;
    x=x-a[tmp]+1;
    int r=t1.dfn[st[tmp]]+t1.size[st[tmp]]-1;
    int l=t1.dfn[st[tmp]]-1;
    return query(rt[r],rt[l],1,n,x);
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<n;i++){
        scanf("%lld%lld",&x,&y);
        t1.add(x,y,1); t1.add(y,x,1);
    }
    t1.dfs(1,0);
    for(int i=1;i<=n;i++){
        rt[i]=rt[i-1];
        insert(rt[i],1,n,t1.pos[i]);
    }
    a[++num]=1; s=n; st[num]=1;
    for(int i=1;i<=m;i++){
        scanf("%lld%lld",&x,&y);
        a[++num]=s+1; s+=t1.size[x];
        tmp=Find(y); top[num]=Get(y,tmp); st[num]=x;
        dis[num]=t1.dis[x];
        t2.add(num,tmp,t1.dis[Get(y,tmp)]-dis[tmp]+1);
        t2.add(tmp,num,t1.dis[Get(y,tmp)]-dis[tmp]+1);
    }
    t2.dfs(1,0);
    for(int i=1;i<=q;i++){
        scanf("%lld%lld",&x,&y);
        tmp1=Find(x),tmp2=Find(y);
        if(tmp1==tmp2){
            ans=t1.dis[Get(x,tmp1)]+t1.dis[Get(y,tmp2)]-2*t1.dis[t1.LCA(Get(x,tmp1),Get(y,tmp2))];
            printf("%lld\n",ans);
            continue;
        }
        ans=0;
        lca=t2.LCA(tmp1,tmp2);
        if(t2.dep[tmp1]<t2.dep[tmp2]) swap(tmp1,tmp2),swap(x,y);
        if(lca!=tmp2){
            ans+=t1.dis[Get(y,tmp2)]-dis[tmp2]+1,y=t2.s_lca(tmp2,lca);
            ans+=t2.dis[tmp2]-t2.dis[y]; y=top[y];
        } else y=Get(y,tmp2);
        ans+=t1.dis[Get(x,tmp1)]-dis[tmp1]+1,x=t2.s_lca(tmp1,lca);
        ans+=t2.dis[tmp1]-t2.dis[x]; x=top[x]; 
        ans+=t1.dis[x]+t1.dis[y]-2*t1.dis[t1.LCA(x,y)];
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值