bzoj 4539: [Hnoi2016]树(缩点+主席树+lca)

20 篇文章 0 订阅
9 篇文章 0 订阅

4539: [Hnoi2016]树

Time Limit: 40 Sec   Memory Limit: 256 MB
Submit: 442   Solved: 173
[ Submit][ Status][ Discuss]

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条边。

Source

[ Submit][ Status][ Discuss]


题解:这道题小范围暴力的话可以考虑从模板树中不断选取点建立大树,然后对大树建立倍增LCA。但是如果每次都这么加的话会产生MN个点,MLE显然,而且也会TLE。所以我们考虑缩点,我们原先是在大树中加入一个子树,现在考虑把整个子树考虑成一个节点,给出的连接点是大树中现在的节点,因为加入时会重新标号的大小顺序是不变的所以一个节点代表的子树的编号一定是一段连续的区间,我们可以通过二分的方式查找到这个点所属的缩点后的节点,然后连接,连接时两点之间的距离为加入的子树的根到该子树的根的距离。(注意最初的模板树也要缩成一点,作为大树的根节点)。

我们需要记录连接的准确节点但是每次都暴力一遍的时间复杂度是不科学的,因为子树中的节点大小有序,所以我们只要知道了这可子树最小的编号,就能确定节点在子树中是第几大。对模板树求DFS序列,这样子树都是连续的区间,求解区间第K大,可以用静态主席树(权值线段树+动态开点)来做。

我们最终会得到一颗m+1个节点的大树。在做LCA的时候考虑先在大树上做LCA,定位到同一个节点的时候再在该节点的内部做LCA,把该节点内部的子树定位回模板树,做lca 即可。但是中间有很多的细节需要注意,如果X,Y的LCA不是X,Y,那么我们在LCA的最后一步不让他们向上跳就可以得到与LCA相连的节点,注意外层的LCA,最后一步不跳也不计入答案,但是ANS要+2,因为我们这两个大的节点连接到LCA上需要一个短边,然后找到这两个节点连接的位置,跳回模板树做LCA即可。

对于其中一个点是LCA的情况,我们需要在他蹦最后一个2^k步的时候,单独处理一下(即在外过程跳2^k-1步,2^0+2^1+...+2^(k-1) ),在进行内部的lca的时候需要考虑原本就在LCA节点内部的点与连接点的位置关系,通过深度的加减运算得到答案即可。

#include<iostream>    
#include<cstdio>    
#include<cstring>    
#include<algorithm>    
#include<queue>    
#include<cmath>    
#define N 200003    
#define LL long long 
using namespace std;    
int q[N],l[N],r[N],n,m,p,sz,belong[N],pre[N],deep1[N];    
LL ll[N],rr[N];    
int point[N],next[N],v[N],fa[N],cnt,tot;
LL len[N],deep[N],f[N],size[N];    
int tree[N*20],sum[N*20],tl[N*20],tr[N*20],root[N];    
LL mi[30],f1[21][N],dis[21][N];    
LL f2[21][N],dis2[21][N];    
int head[N],nxt[N],v1[N],tt,jd;    
int nowx,nowy,lcanow;    
bool pd;    
void add(int x,int y)    
{    
    tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;    
    tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x;    
}    
void build(int x,int y,int z)    
{    
    tt++; nxt[tt]=head[x]; head[x]=tt; v1[tt]=y; len[tt]=z;    
    tt++; nxt[tt]=head[y]; head[y]=tt; v1[tt]=x; len[tt]=z;    
}    
void dfs(int x,int f)    
{    
    for (int i=1;i<=20;i++)    
    {    
        if (deep[x]-mi[i]<0) break;    
        f2[i][x]=f2[i-1][f2[i-1][x]];    
        dis2[i][x]=dis2[i-1][x]+dis2[i-1][f2[i-1][x]];    
    }    
    fa[x]=f; q[++cnt]=x; l[x]=cnt; size[x]=1;    
    for (int i=point[x];i;i=next[i])    
    if (v[i]!=f)    
    {    
        deep[v[i]]=deep[x]+1;    
        f2[0][v[i]]=x;  dis2[0][v[i]]=1;    
        dfs(v[i],x);    
        size[x]+=size[v[i]];    
    }    
    r[x]=cnt;    
}    
void insert(int &i,int l,int r,int x)    
{    
    tree[++sz]=tree[i]; sum[sz]=sum[i]; tl[sz]=tl[i]; tr[sz]=tr[i];    
    i=sz;    
    sum[i]++;    
    if (l==r)  return ;    
    int mid=(l+r)/2;    
    if (x<=mid)  insert(tl[i],l,mid,x);    
    else insert(tr[i],mid+1,r,x);    
}    
int query(int i,int j,int l,int r,int k)    
{    
    if (l==r)  return l;    
    int t=sum[tl[j]]-sum[tl[i]];    
    int mid=(l+r)/2;    
    if (t>=k)  return query(tl[i],tl[j],l,mid,k);    
    else return query(tr[i],tr[j],mid+1,r,k-t);    
}    
int find(LL x)    
{    
    int l=1; int r=jd;    
    while(l<=r)    
    {    
        int mid=(l+r)/2;     
        if (x<=f[mid-1])  r=mid-1;    
        else if (x>f[mid])  l=mid+1;    
        else  return mid;    
    }    
}    
LL lca(int x,int y)    
{    
    if (deep[x]<deep[y]) swap(x,y);    
    int k=deep[x]-deep[y];    
    LL ans=0;    
    for (int i=0;i<=20;i++)    
     if (k>>i&1) {    
        ans+=dis2[i][x];    
        x=f2[i][x];    
     }   
    lcanow=y;   
    if (x==y)  return ans;    
    for (int i=20;i>=0;i--)    
     if (f2[i][y]!=f2[i][x])    
      {    
        ans+=dis2[i][x];    
        ans+=dis2[i][y];    
        x=f2[i][x]; y=f2[i][y];    
      }   
    lcanow=f2[0][x];   
    return ans+2;    
}    
void dfs1(int x,int f)    
{    
    for(int i=1;i<=20;i++)    
    {    
        if (deep1[x]-mi[i]<0)  break;    
        f1[i][x]=f1[i-1][f1[i-1][x]];    
        dis[i][x]=dis[i-1][x]+dis[i-1][f1[i-1][x]];    
    }     
    for (int i=head[x];i;i=nxt[i])    
    if (v1[i]!=f)    
    {    
        deep1[v1[i]]=deep1[x]+1;    
        f1[0][v1[i]]=x; dis[0][v1[i]]=len[i];    
        dfs1(v1[i],x);    
    }    
} 
void chuli(int k,int x)
{
    for (int i=0;i<k;i++)
     x=f1[i][x];
    nowy=x;
}   
LL lca1(int x,int y)    
{    
    if (deep1[x]<deep1[y]) swap(x,y);     
    int k=deep1[x]-deep1[y];    
    LL ans=0;  
    for (int i=0;i<=20;i++)
     if (k==mi[i])
      {
        chuli(i,x);
        break;
      }
    int t=k;
    for (int i=20;i>=0;i--)    
     if (k>>i&1)  {    
        t-=(1<<i);
        ans+=dis[i][x];    
        x=f1[i][x];
        for (int j=0;j<=20;j++)
        if (t==mi[j])
        {
            chuli(j,x);
            break;
        }    
     }    
    if (x==y) {    
        pd=false; nowx=x;    
        return ans;    
    }    
    for (int i=20;i>=0;i--)    
     if (f1[i][x]!=f1[i][y])    
      {    
        ans+=dis[i][x]; ans+=dis[i][y];    
        x=f1[i][x]; y=f1[i][y];    
      }       
    nowx=x; nowy=y;    
    return ans+2;    
}    
int main()    
{    
    scanf("%d%d%d",&n,&m,&p);    
    mi[0]=1;    
    for(int i=1;i<=20;i++)  mi[i]=mi[i-1]*2;    
    for (int i=1;i<n;i++)    
    {    
        int x,y; scanf("%d%d",&x,&y);    
        add(x,y);    
    }    
    deep[1]=1;    
    dfs(1,0);     
    sz=0; root[0]=0;    
    for (int i=1;i<=n;i++)    
    {    
        root[i]=root[i-1];    
        insert(root[i],1,n,q[i]);    
    }    
    f[0]=0; f[1]=size[1]; jd=1;    
    ll[1]=1; rr[1]=size[1]; belong[1]=1;    
    for (int i=1;i<=m;i++)    
    {    
        int a; LL b; scanf("%d%lld",&a,&b);    
        int t=find(b);    
        LL now=b-f[t-1];  //在小树DFS序中的排名     
        int rank=query(root[ll[t]-1],root[rr[t]],1,n,(int)now);//在q中的编号     
        jd++;    
        f[jd]=f[jd-1]+size[a];      
        ll[jd]=l[a]; rr[jd]=r[a]; belong[jd]=a; pre[jd]=rank;    
        build(jd,t,deep[rank]-deep[belong[t]]+1);    
    }    
    deep1[1]=1; dfs1(1,0);      
    for (int i=1;i<=p;i++)    
    {    
        LL x,y; scanf("%lld%lld",&x,&y);    
        int x0=find(x); int y0=find(y);    
        if (x0==y0)    
        {    
            int t=query(root[ll[x0]-1],root[rr[x0]],1,n,x-f[x0-1]);    
            int t1=query(root[ll[y0]-1],root[rr[y0]],1,n,y-f[y0-1]);      
            printf("%lld\n",lca(t,t1));    
            continue;    
        }    
        pd=true;    
        LL ans=lca1(x0,y0);    
        if (!pd)  {    
            if (deep1[x0]<deep1[y0])  swap(x0,y0),swap(x,y);    
            int t=query(root[ll[x0]-1],root[rr[x0]],1,n,x-f[x0-1]);    
            ans+=deep[t]-deep[belong[x0]];    
            int rank=query(root[ll[nowx]-1],root[rr[nowx]],1,n,y-f[nowx-1]);    
            LL k=lca(rank,pre[nowy]);   
            if (lcanow==rank)  printf("%lld\n",ans-(deep[rank]-deep[belong[nowx]]));   
            else printf("%lld\n",ans-(deep[pre[nowy]]-deep[belong[nowx]])+k);   
        }    
        else    
        {    
         int t=query(root[ll[x0]-1],root[rr[x0]],1,n,x-f[x0-1]);    
         int t1=query(root[ll[y0]-1],root[rr[y0]],1,n,y-f[y0-1]);    
         ans+=deep[t]-deep[belong[x0]];   
         ans+=deep[t1]-deep[belong[y0]];    
         printf("%lld\n",ans+lca(pre[nowx],pre[nowy]));    
        }    
    }    
}    


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值