HNOI2016D1T3 树 题解

本文介绍了一种基于树分块的算法实现方案,用于解决特定的大规模树形结构查询问题。该算法通过将树结构复制并分块处理,有效地降低了查询复杂度。文章详细讲解了算法的具体步骤,包括树的复制、块的构建、节点间距离的计算等,并提供了完整的源代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(题目描述略)

纯数据结构题,不管是树分块也好,还是树套树也罢。基本思想是将每一次复制操作中新增的所有点看作一个块,这样加上初始的模板树也看作一个块,若复制了 m 次则将大树分成 m + 1 块。将大树看作是 m + 1 个节点构成的树,每个节点代表一个块,相邻节点间距离为相应块中深度最小节点的深度差。每次求两个节点间距离时,将两个节点所在块的编号求出,然后将路径分为块内路径和块间路径,分别解决求和即可。

首先对模板树深度优先搜索,完成相应的倍增求最近公共祖先的预处理。对于第 i 次将模板树中以 fr[i] 为根的子树复制到大树中 to[i] 节点下的操作,通过记录每次复制后大树的总节点数,二分求出 to[i] 节点所在块编号 to1[i],用求区间第 k 大的方法求出 to[i] 节点在模板树中的编号 to0[i],此处需维护一个划分树或主席树。然后,在块 to1[i] 和块 i 间建一条从 to1[i] 指向 i 的,权值为模板树中编号为 fr[to1[i]] 和 to0[i] 间深度差加一的有向边。在 m 次复制操作完成后,对 m + 1 个块构成的树深度优先搜索,完成相应的倍增求最近公共祖先的预处理。

对于大树中编号为 u 和编号为 v 的节点间距离的查询,首先二分求出 u 和 v 节点所在块编号 u1 和 v1,并求出其分别在块内距离相应块的根的距离。而后,通过倍增数组将 u1 和 v1 上移至同一深度,再一同上移,同时记录路径权值和,直至到达以下状态之一:
状态 1:u1 和 v1 块的父节点相同,且 u1 和 v1 块节点不相同。将权值和加上模板树中编号为 to0[u1] 和 to0[v1] 节点间的距离即为答案,而后者可以通过求模板树中两节点的最近公共祖先而得到。
状态 2:u1 块的父节点为 v1 块,或 v1 块的父节点为 u1 块。不失一般性的,不妨令 u1 块的父节点为 v1 块,则必有 v1 节点块无上移过程(否则为状态 1),求出大树中编号为 v 节点在模板树中的编号 v0,则将权值和加上模板树中编号为 to0[u1] 和 v0 节点间的距离即为答案。
状态 3:u1 块和 v1 块相同。此时必有 u1 和 v1 节点块无上移过程(否则若有一个节点块存在上移过程则为状态 2,若两个节点块均存在上移过程则为状态 1),求出大树中编号为 u 和 v 节点在模板树中的编号 u0 和 v0,则模板树中编号为 u0 和 v0 节点间的距离即为答案。

由于数据规模较大,加上此算法常数较大,故需加上输入输出优化此算法才能勉强通过测评。由于复制过程大树节点数可能超过 integer 整型范围,故在必要的地方数值需用 long long integer 超长整型表示。

代码如下:

#include"stdio.h"
#include"string.h"
#define MAX_N (100005)
#define Exchange(a,b,type) \
{ \
    type __tmp_a=a; \
    a=b,b=__tmp_a; \
}
#define Read(x) \
{ \
    char __tmp_c=getchar();x=0; \
    while(__tmp_c<'0'||__tmp_c>'9') \
        __tmp_c=getchar(); \
    while(__tmp_c>='0'&&__tmp_c<='9') \
        x=x*10+__tmp_c-'0', \
        __tmp_c=getchar(); \
}
char str[25];
int dep0[MAX_N],dep1[MAX_N],fedg0[MAX_N],fedg1[MAX_N],ft[MAX_N],_index=0,m,prev0[MAX_N][20],prev1[MAX_N][20],st[MAX_N];
long long dist[MAX_N];
struct E
{
    int next,to;
}edge0[MAX_N<<1],edge1[MAX_N];
struct PARTITION_TREE
{
    int sum[20][MAX_N],val[20][MAX_N];
    void build(int f,int l,int r)
    {
        int m=(l+r+1)/2,pl=l,pr=(l+r+1)/2;
        for(int i=l;i<r;i++)
        {
            sum[f][i]=i>l?sum[f][i-1]:0;
            if(m>val[f][i])
                val[f+1][pl++]=val[f][i],++sum[f][i];
            else
                val[f+1][pr++]=val[f][i];
        }
        if(l+1<m)
            build(f+1,l,m);
        if(m+1<r)
            build(f+1,m,r);
    }
    int query(int f,int l,int r,int ql,int qr,int p)
    {
        if(ql==qr)
            return val[f][ql];
        int m=(l+r+1)/2,lt=l<ql?sum[f][ql-1]:0;
        if(p<=sum[f][qr]-lt)
            return query(f+1,l,m,l+lt,l+sum[f][qr]-1,p);
        else
            return query(f+1,m,r,m+ql-l-lt,m+qr-l-sum[f][qr],p-sum[f][qr]+lt);
    }
}partree;
struct COPY
{
    int fr,to0,to1;
    long long sum,to;
}maintain[MAX_N];
void dfs0(int now,int fah)
{
    st[now]=++_index;
    partree.val[0][_index]=now;
    dep0[now]=dep0[fah]+1,prev0[now][0]=fah;
    for(int i=0;prev0[now][i]>-1;i++)
        prev0[now][i+1]=prev0[prev0[now][i]][i];
    for(int i=fedg0[now];i>-1;i=edge0[i].next)
        if(edge0[i].to!=fah)
            dfs0(edge0[i].to,now);
    ft[now]=_index;
}
int biquery(int rt,long long p)
{
    int lt=0,md;
    while(lt+1<rt)
    {
        md=(lt+rt)/2;
        if(maintain[md].sum>=p)
            rt=md;
        else
            lt=md;
    }
    return rt;
}
void dfs1(int now,int fah)
{
    dep1[now]=dep1[fah]+1,prev1[now][0]=fah;
    dist[now]=dist[fah]+dep0[maintain[now].to0]-dep0[maintain[fah].fr]+1;
    for(int i=0;prev1[now][i]>-1;i++)
        prev1[now][i+1]=prev1[prev1[now][i]][i];
    for(int i=fedg1[now];i>-1;i=edge1[i].next)
        dfs1(edge1[i].to,now);
}
int query0(int u,int v)
{
    int res=dep0[u]+dep0[v];
    if(dep0[u]<dep0[v])
        Exchange(u,v,int);
    for(int i=19;i>=0&&dep0[u]>dep0[v];i--)
        if(1<<i<=dep0[u]-dep0[v])
            u=prev0[u][i];
    for(int i=19;i>=0&&u!=v;i--)
        if(prev0[u][i]!=prev0[v][i])
            u=prev0[u][i],v=prev0[v][i];
    if(u!=v)
        u=prev0[u][0],v=prev0[v][0];
    return res-2*dep0[u];
}
long long query1(long long fr,long long to)
{
    int fr0,to0,u=biquery(m+1,fr),v=biquery(m+1,to);
    fr0=partree.query(0,1,_index+1,st[maintain[u].fr],ft[maintain[u].fr],(int)(fr-maintain[u-1].sum));
    to0=partree.query(0,1,_index+1,st[maintain[v].fr],ft[maintain[v].fr],(int)(to-maintain[v-1].sum));
    long long res=dep0[fr0]-dep0[maintain[u].fr]+dep0[to0]-dep0[maintain[v].fr]+dist[u]+dist[v];
    if(dep1[u]<dep1[v])
    {
        Exchange(fr,to,long long);
        Exchange(fr0,to0,int);
        Exchange(u,v,int);
    }
    for(int i=19;i>=0&&dep1[u]>dep1[v];i--)
        if(1<<i<dep1[u]-dep1[v])
            u=prev1[u][i];
    if(dep1[u]==dep1[v]+1&&prev1[u][0]!=v)
        u=prev1[u][0];
    if(dep1[u]==dep1[v])
        for(int i=19;i>=0&&u!=v;i--)
            if(prev1[u][i]!=prev1[v][i])
                u=prev1[u][i],v=prev1[v][i];
    res-=dist[u]+dist[v]-2;
    if(u!=v&&dep1[u]==dep1[v])
        fr0=maintain[u].to0,to0=maintain[v].to0;
    else if(u!=v)
        fr0=maintain[u].to0,res-=dep0[to0]-dep0[maintain[v].fr]+1;
    else
        res-=dep0[fr0]-dep0[maintain[u].fr]+dep0[to0]-dep0[maintain[v].fr]+2;
    res+=query0(fr0,to0);
    return res;
}
void Write(long long x)
{
    if(x==0)
        putchar('0');
    for(str[0]=0;x>0;x/=10)
        str[++str[0]]=x%10+'0';
    while(str[0]>0)
        putchar(str[str[0]--]);
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    int q;long long fr,to;
    Read(maintain[1].sum);Read(m);Read(q);
    memset(fedg0,-1,sizeof(fedg0));
    for(int i=1;i<maintain[1].sum;i++)
    {
        Read(edge0[i<<1].to);Read(edge0[i<<1^1].to);
        edge0[i<<1].next=fedg0[edge0[i<<1^1].to];
        edge0[i<<1^1].next=fedg0[edge0[i<<1].to];
        fedg0[edge0[i<<1^1].to]=i<<1;
        fedg0[edge0[i<<1].to]=i<<1^1;
    }
    memset(prev0,-1,sizeof(prev0));
    dep0[0]=-1,dfs0(1,0);
    partree.build(0,1,_index+1);
    maintain[1].fr=1,maintain[0].sum=maintain[1].to=maintain[1].to0=maintain[1].to1=0;
    memset(fedg1,-1,sizeof(fedg1));
    for(int i=2;i<=m+1;i++)
    {
        Read(maintain[i].fr);Read(maintain[i].to);
        maintain[i].to1=biquery(i,maintain[i].to);
        maintain[i].to0=partree.query(0,1,_index+1,st[maintain[maintain[i].to1].fr],ft[maintain[maintain[i].to1].fr],maintain[i].to-maintain[maintain[i].to1-1].sum);
        maintain[i].sum=maintain[i-1].sum+ft[maintain[i].fr]-st[maintain[i].fr]+1;
        edge1[i].next=fedg1[maintain[i].to1],edge1[i].to=i;
        fedg1[maintain[i].to1]=i;
    }
    memset(prev1,-1,sizeof(prev1));
    dep1[0]=dist[0]=-1,dfs1(1,0);
    while(q--)
    {
        Read(fr);Read(to);
        Write(query1(fr,to));putchar('\n');
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值