树链剖分边权版

博客围绕树链剖分边权版展开,但具体内容缺失。树链剖分边权版是信息技术领域算法相关内容,可用于解决树上路径问题等。

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

int tol,head[maxn];
struct edge
{
    int to,next;
}es[maxm];
void addedge( int u , int v )
{
    es[tol].to = v;
    es[tol].next = head[u];
    head[u] = tol++;
}
int top[maxn],fa[maxn],dep[maxn],sizes[maxn],tid[maxn],rk[maxn],wson[maxn],num;
void init()
{
    tol = 0;
    memset ( head , -1 , sizeof(head) );
    num = 0;
    memset ( wson , -1 , sizeof(wson) );
}
void dfs1( int u , int f , int d )
{
    dep[u] = d,fa[u] = f,sizes[u] = 1;
    for ( int i=head[u] ; i!=-1 ; i=es[i].next )
    {
        int v = es[i].to;
        if ( v!=f )
        {
            dfs1 ( v , u , d+1 );
            sizes[u] += sizes[v];
            if ( wson[u]==-1||sizes[v]>sizes[wson[u]] )
                wson[u] = v;
        }
    }
}
void dfs2( int u , int tp )
{
    top[u] = tp,tid[u] = ++num,rk[tid[u]] = u;
    if ( wson[u]==-1 ) return;
    dfs2( wson[u] , tp );
    for ( int i=head[u] ; i!=-1 ; i=es[i].next )
        if ( es[i].to!=fa[u]&&es[i].to!=wson[u] ) dfs2( es[i].to , es[i].to );
}
dfs1( 1 , 0 , 1 );
dfs2( 1 , 1 );
for ( int i=2 ; i<=n ; i++ )
{
    if ( dep[u[i]]<dep[v[i]] ) swap ( u[i] , v[i] );
    val[u[i]] = w[i];
}
void Change( int u , int v )
{
    while ( top[u]!=top[v] )
    {
        if ( dep[top[u]]<dep[top[v]] ) swap ( u , v );
        Update( tid[top[u]] , tid[u] , 2 , n , 1 );
        u = fa[top[u]];
    }
    if ( dep[u]>dep[v] ) swap ( u , v );
    Update( tid[u]+1 , tid[v] , 2 , n , 1 );
}

 

<think>嗯,用户让我介绍一下长链剖分,特别是它在处理可合并的与深度有关的子树信息方面的应用,比如某深度的点数或者点和。我需要先回忆一下长链剖分的基本概念,然后思考它为什么适合处理这类问题。 首先,长链剖分是什么?我记得它和重链剖分类似,但重链剖分选择子树大小最大的子节点作为重儿子,而长链剖分选择的是最深子树深度最大的子节点作为长儿子。这样形成的链叫做长链,每个长链的长度对应其深度。那这样剖分后的结构有什么特点呢?可能每个节点属于一条长链,而长链之间通过轻连接。 接下来,长链剖分的应用场景。用户提到的是处理与深度相关的子树信息,例如统计某深度的点数和点和。这类问题通常需要收集每个子树中不同深度的信息,然后将这些信息合并起来。如果直接暴力合并,时间复杂度可能会很高,尤其是当树退化成链的时候,复杂度可能达到O(n²)。而长链剖分的优势在于通过指针移动来共享内存,从而将时间复杂度降低到O(n),对吗? 比如,对于每个节点,处理其所有轻儿子(非长儿子)的长链,然后将这些轻儿子的信息合并到当前节点的链上。由于长儿子的信息可以直接通过指针传递,不需要复制,这样就节省了时间。具体来说,当计算某个节点的深度信息时,可以直接继承其长儿子的数据,然后遍历其他子节点,将它们的深度信息逐层合并到当前的数据结构中。这样每个长链只会被合并一次,总的操作次数是线性的。 举个例子,比如统计每个深度的节点数量。对于每个节点u,假设它的深度是d,那么它的子节点v的深度信息需要合并到u的深度d+1的位置。如果v是长儿子,那么u可以直接使用v的数组,通过指针偏移,不需要复制数据。其他子节点的数据则逐个深度合并到u的数组中。因为每个节点只在所在长链的头部被处理一次,所以总的时间复杂度是O(n)。 另外,需要提到的是,这种优化通常需要用到动态数组,比如在C++中可以使用vector,或者预先分配内存,通过指针偏移来实现。例如,处理长儿子时,父节点直接使用子节点的数组空间,这样父节点的数组起始位置比子节点少一位,这样父节点的深度d对应的数据就是子节点深度d+1的数据,从而直接继承。 可能需要注意的问题包括,如何高效地合并数据,以及如何处理不同的操作(比如求和、最大值等)。另外,长链剖分主要适用于静态树结构,因为一旦树的结构发生变化,长链可能需要重新剖分,这会增加维护的成本。 用户可能的应用场景是在算法竞赛中遇到需要高效统计子树深度信息的问题,比如子树中各个深度的节点数或者值和,这时候长链剖分可以提供一个线性时间的解决方案,相比传统的O(n²)方法更高效。用户可能已经了解重链剖分,但对长链剖分不太熟悉,所以需要解释两者的区别和适用情况。 总结一下,长链剖分的核心思想是通过重用长儿子的内存空间,减少重复操作,从而将时间复杂度优化到线性。在处理深度相关的子树信息合并时,这种方法非常有效,特别是当问题满足可合并的性质时,比如加法或取最大值等操作。</think>长链剖分是一种用于高效处理树结构问题(尤其是与**子树深度信息**相关)的算法技巧,其核心思想是通过复用内存和指针偏移,将时间复杂度优化至线性(O(n))。以下是其核心原理和应用场景的详解: --- ### **一、长链剖分基础** #### 1. **定义** - **长链**:每个节点选择**子树深度最大**的子节点作为「长儿子」,由此延伸出的链称为长链(类比重链剖分中的「重儿子」)。 - **轻**:非长儿子对应的。 - **关键性质**:每个节点属于且仅属于一条长链,长链总数为 O(n),且每条长链的长度等于链头节点的深度。 #### 2. **剖分目的** 将树分解为若干长链,通过**复用长链的内存空间**,避免重复计算,从而高效处理与深度相关的子树信息。 --- ### **二、适用问题类型** 长链剖分擅长处理**可合并的、与深度相关的子树统计问题**,例如: 1. 子树中某深度的节点数(如:统计所有子树中深度为 k 的节点数量)。 2. 子树中某深度的点和(如:查询子树中深度为 k 的所有节点的值之和)。 3. 其他可逐层合并的信息(如:最大值、众数等)。 --- ### **三、算法核心思想** #### 1. **内存复用与指针偏移** - **继承长儿子数据**:父节点直接复用长儿子的数组空间,通过指针偏移(如父节点深度 d 的数组位置对应长儿子深度 d+1 的位置)。 - **合并轻儿子数据**:对每个轻儿子的长链,逐层合并其深度信息到当前链中。 #### 2. **操作步骤** 1. **预处理长链**:通过一次 DFS 确定每个节点的长儿子。 2. **动态规划合并**: - 自底向上遍历,优先处理长儿子。 - 对每个节点,先继承长儿子的深度信息(O(1) 时间)。 - 遍历轻儿子,将其深度信息合并到当前链中(合并总次数为 O(n))。 --- ### **四、时间复杂度分析** - **预处理长链**:O(n)。 - **合并操作**:每个轻链的合并代价等于其长度,所有轻链总长度为 O(n)。 - **总复杂度**:O(n),显著优于暴力 O(n²)。 --- ### **五、示例:统计子树各深度的节点数** #### 1. **问题描述** 对树中每个节点 u,求其子树中深度为 k 的节点数(对所有可能的 k)。 #### 2. **长链剖分实现** - **数据结构**:每个节点维护一个数组 `cnt[]`,`cnt[d]` 表示子树中深度为 d 的节点数。 - **合并过程**: 1. 节点 u 继承长儿子 v 的 `cnt[]` 数组(直接指针偏移,无需复制)。 2. 对 u 的其他子节点 w(轻儿子): - 递归计算 w 的 `cnt[]`。 - 将 `w.cnt[d]` 合并到 `u.cnt[d+1]`。 3. 最后处理 u 本身:`u.cnt[0] = 1`。 #### 3. **伪代码** ```python def dfs(u, parent): if u 是叶子节点: u.cnt = [1] # 深度0的节点数为1 return 长儿子 v = u 的长儿子 dfs(v, u) u.cnt = v.cnt # 直接继承(通过指针偏移) u.cnt.insert(0, 1) # 当前节点深度0的计数 for 子节点 w in u 的子节点 (w ≠ v): dfs(w, u) for d in 0 to len(w.cnt)-1: u.cnt[d+1] += w.cnt[d] # 合并轻儿子的深度信息 ``` --- ### **六、优势与局限性** #### 1. **优势** - **线性时间复杂度**:完美处理大规模树结构。 - **低内存开销**:通过指针复用避免冗余存储。 #### 2. **局限性** - **静态树结构**:动态树需要重新剖分。 - **信息可合并性**:需满足合并操作的结合律(如加法、取最大值)。 --- ### **七、扩展应用** 1. **子树 k 级祖先查询**:结合长链剖分与倍增法。 2. **树上路径问题**:如最长路径(直径)的统计。 3. **动态规划优化**:如树上依赖背包问题的加速。 长链剖分通过巧妙的内存管理和合并策略,成为处理深度相关子树统计的高效工具,尤其适合算法竞赛或需要处理大规模树结构的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值