树链剖分(初学)

树链剖分是一种用于解决树上两点间路径上的最大最小值查询与修改的高效方法。通过将树剖分为多条链,可以优化操作的时间复杂度。文中介绍了树链剖分的基本概念,如重儿子、轻儿子、重边、轻边和重链,并详细解释了树链剖分所需的变量,如父亲节点、深度、子树大小、重儿子、所在链顶点和重新编号。此外,还通过软件包管理器和Tree两道题目的例子展示了树链剖分的实际应用,强调了区间修改的线段树和懒标记的正确使用。
摘要由CSDN通过智能技术生成

首先看一下树链剖分是什么东西:

当我们要同时解决以下两个问题时:

1.求树上两点简单路径上的最大最小值(和)

2.修改树上两点简单路径上的边权(点权)

如果只用单独考虑,那么第一个就是树上差分,第二个是倍增,可是如果同时要有两个操作,那么时间复杂度就会大幅度退化,所以现在要用一个新的方法:树链剖分

它将一棵树剖成了几条链来组成,且每个点最多只会存在一条链上,且只出现一次。那么怎么进行剖分呢?

首先定义一些东西:

重儿子:父亲结点的所有儿子结点中,子树拥有结点数最多的结点。

轻儿子:父亲结点除了重儿子以外的所有儿子。

重边:父亲结点与重儿子的连边。

轻边:父亲结点与轻儿子的连边。

重链:所有重边所组成的一条链。

就是这个样子,粗的边就是重边,不过链的长度可以为0,就是只有一个点

 在进行树链剖分时,我们将进行这些变量声明:
f[u]:保存点u的父亲结点
d[u]:保存点u的深度
size[u]:保存以u为根节点的子树的结点个数
son[u]:保存点u的重儿子
top[u]:保存点u所在重链的顶端结点
id[u]:保存点u进行重新编号后的新的编号

然后就可以开始搞了:

inline void dfs( int x , int f ){
    sz[x] = 1;
    fa[x] = f;
    for( register int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( v == f ) continue;
        depth[v] = depth[x] + 1;//求深度
        dfs( v , x );
        sz[x] += sz[v];//确定son[x],用sz[v]较大的v坐
        if( sz[son[x]] < sz[v] )
            son[x] = v;
    }
}
inline void dfs2( int x , int f ){
    top[x] = f;//这条链的最高点
    id[x] = ++cnt;//进行编号
    if( !son[x] )
        return ;
    dfs2( son[x] , f );//先找重边,再找轻边
    for( register int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( id[v] ) continue;
        dfs2( v ,v );
    }
}
void get_ans( int x ){
    int fx = top[x] , y = 1 , fy = top[1];//x,y的简单路径上的查询,这里y=1
    int sum = 0;
    while( fx != fy ){
        if( depth[fx] >= depth[fy] ){
            ans = 0;
            query( 1 , id[fx] , id[x] );//注意谁的编号大
            sum += ans;
            insert_( 1 , id[fx] , id[x] , 2 );
            x = fa[fx];fx = top[x];
        }
        else{
            ans = 0;
            query( 1 , id[fy] , id[y] );
            sum += ans;
            insert_( 1 , id[fy] , id[y] , 2 );
            y = fa[fy];fy = top[y];
        }
    }//在最后一个链内,还需要判断它们内部的距离
    if( depth[x] <= depth[y] ){
        ans = 0;
         query( 1 , id[x] , id[y] );
        sum += ans;
        insert_( 1 , id[x] , id[y] , 2 );
    }
    else{
        ans = 0;
        query( 1 , id[y] , id[x] );
        sum += ans;
        insert_( 1 , id[y] , id[x] , 2 );
    }
    printf( "%d\n" , sum );
}

先来一个板题:软件包管理器

我们要知道一个性质,对于一个点x,它的子树编号一定是比他大的,这样对于子树查找与修改就很简单了

然后区间修改就用线段树即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <algorithm>
#define ll long long
using namespace std;
const int MAXN = 100013;
int n;
struct node{
    int l , r;
    int lazy , sum;
}tre[MAXN*4];
int sz[MAXN] , son[MAXN] , top[MAXN] , depth[MAXN] , id[MAXN] , fa[MAXN] , cnt;
vector<int> G[MAXN];
inline void dfs( int x , int f ){
    sz[x] = 1;
    fa[x] = f;
    for( register int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( v == f ) continue;
        depth[v] = depth[x] + 1;
        dfs( v , x );
        sz[x] += sz[v];
        if( sz[son[x]] < sz[v] )
            son[x] = v;
    }
}
inline void dfs2( int x , int f ){
    top[x] = f;
    id[x] = ++cnt;
    if( !son[x] )
        return ;
    dfs2( son[x] , f );
    for( register int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( id[v] ) continue;
        dfs2( v ,v );
    }
}
inline void build( int i , int l , int r ){
    tre[i].l = l , tre[i].r = r;
    tre[i].sum = tre[i].r - tre[
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值