【知识点】树链剖分

摘要:
树链划分是通过某种策略将原始的树链划分为若干链,每个链相当于一个序列。此时,您可以使用间隔数据结构来维护这些链。实现步骤:1.一旦$dfs$获得前四个值,然后$dfs美元再次重新排序树的节点,以使重链上的点$dfs$的顺序连续。

树链剖分:

用于解决一系列维护静态树上信息的问题。这些问题看起来非常像一些区间操作搬到了树上。

(例如:一棵带权树,需要维护修改权值操作以及从$u$到$v$简单路径上的权值和)

树链剖分就是通过某种策略(一般是轻、重边剖分)将原树链划分成若干条链,每条链相当于一个序列,此时就可以用区间数据结构(一般是线段树)维护这些链。

需要维护的值:

$f(x)$:$x$在树中的父亲。

$dep(x)$:$x$在树中的深度。

$siz(x)$:$x$的子树大小。

$son(x)$:$u$的重儿子:在$u$的所有儿子中$siz$值最大的儿子,$u ightarrow v$为重边。

($u$的轻儿子:在$u$的所有儿子中除了重儿子以外的儿子,$u ightarrow v$为轻边。)

$top(x)$:$x$所在重路径的顶部节点。

$seg(x)$:$x$在线段树中的位置(下标)。

$rnk(x)$:线段树中$x$位置对应的树中节点编号,即有$rnk(seg(x))=x$。

轻重边的一些性质:

1、如果$u ightarrow v$为轻边,则$siz(v)<=siz(u)/2$。

证明:反证法,若存在$siz(v)>siz(u)/2$且存在$siz(v_0)>siz(v)$,那么$siz(v)+siz(v_0)>siz(u)$,即子节点的$siz$和大于父节点的$siz$。

2、从根到任何点$u$的路径上轻边的条数不超过$log(N)$。

证明:由1可知从根到$u$的路径上每经过一条轻边,当前子树的节点个数至少会少$frac{1}{2}$,所以至多减少$log(N)$次$siz$值为0,到达叶节点。

3、从根到任何点$u$的路径上轻边、重边的条数均不超过$log(N)$。

证明:每条重链的起点和终点都连接一条轻边,由2可知轻边条数不超过$log(N)$,所以重链条数也不超过$log(N)$。

实现步骤:

1、一遍$dfs$得到前4个值,再一遍$dfs$将树的节点重新排序,使一条重链上的点$dfs$序连续。

2、使用线段树维护新树的$dfs$序序列,查询时沿重链走到两点的$lca$并计算答案。

模板题目:loj10138

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>

using namespace std;
#define MAXN 100005
#define MAXM 500005
#define INF 0x7fffffff
#define ll long long

int hd[MAXN],to[MAXN<<1],top[MAXN];
int A[MAXN],nxt[MAXN<<1],cnt,tot;
int f[MAXN],siz[MAXN],son[MAXN];
int seg[MAXN],rnk[MAXN],dep[MAXN];
struct node{int l,r,sum,mx;}tr[MAXN<<2];
char str[10];

inline int read(){
    int x=0,f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar())
        if(c=='-')
            f=-1;
    for(;isdigit(c);c=getchar())
        x=x*10+c-'0';
    return x*f;
}

inline void add(int u,int v){
    to[++cnt]=v,nxt[cnt]=hd[u];
    hd[u]=cnt;return;
}

inline void pushup(int k){
    tr[k].mx=max(tr[k<<1].mx,tr[k<<1|1].mx);
    tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
    return;
}

inline void dfs1(int u,int fa,int d){
    dep[u]=d;f[u]=fa;siz[u]=1;
    for(int i=hd[u];i;i=nxt[i]){
        int v=to[i];
        if(v==fa) continue;
        dfs1(v,u,d+1);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]])
            son[u]=v;
    }
    return;
}

inline void dfs2(int u,int fa,int tp){
    top[u]=tp;seg[u]=++tot;rnk[tot]=u;
    if(son[u]) dfs2(son[u],u,tp);
    for(int i=hd[u];i;i=nxt[i]){
        int v=to[i];
        if(v==fa || v==son[u]) continue;
        dfs2(v,u,v);
    }
    return;
}

inline void build(int L,int R,int k){
    tr[k].l=L,tr[k].r=R;
    if(L==R){
        tr[k].mx=tr[k].sum=A[rnk[L]];
        return;
    }
    int mid=(L+R)>>1;
    build(L,mid,k<<1);
    build(mid+1,R,k<<1|1);
    pushup(k);return;
}

inline void update(int x,int y,int k){
    if(tr[k].l==tr[k].r){
        tr[k].mx=tr[k].sum=y;
        return;
    }
    int mid=(tr[k].l+tr[k].r)>>1;
    if(x<=mid) update(x,y,k<<1);
    else update(x,y,k<<1|1);
    pushup(k);return;
}

inline int qmx(int L,int R,int k){
    if(L<=tr[k].l && tr[k].r<=R)
        return tr[k].mx;
    int mid=(tr[k].l+tr[k].r)>>1;
    if(L<=mid && R>mid) 
        return max(qmx(L,R,k<<1),qmx(L,R,k<<1|1));
    else if(R<=mid) return qmx(L,R,k<<1);
    else return qmx(L,R,k<<1|1);
}

inline int qsum(int L,int R,int k){
    if(L<=tr[k].l && tr[k].r<=R)
        return tr[k].sum;
    int mid=(tr[k].l+tr[k].r)>>1;
    if(L<=mid && R>mid) 
        return qsum(L,R,k<<1)+qsum(L,R,k<<1|1);
    else if(R<=mid) return qsum(L,R,k<<1);
    else return qsum(L,R,k<<1|1);
}

inline int solve1(int u,int v){
    int ans=-INF;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ans=max(ans,qmx(seg[top[u]],seg[u],1));
        u=f[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    ans=max(ans,qmx(seg[v],seg[u],1));
    return ans;
}

inline int solve2(int u,int v){
    int ans=0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ans+=qsum(seg[top[u]],seg[u],1);
        u=f[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    ans+=qsum(seg[v],seg[u],1);
    return ans;
}

int main(){
    int N=read();
    for(int i=1;i<N;i++){
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    for(int i=1;i<=N;i++) A[i]=read();
    dfs1(1,0,1);dfs2(1,0,1);build(1,N,1);
    int M=read();
    while(M--){
        cin>>str;int x=read(),y=read();
        if(str[0]=='C') update(seg[x],y,1);
        else if(str[1]=='M') printf("%d
",solve1(x,y));
        else printf("%d
",solve2(x,y));
    }
    return 0;
}

免责声明:文章转载自《【知识点】树链剖分》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇批处理for命令详解(转)多线程下C#如何保证线程安全?下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

#pragma用法详解

Author :Jeffrey  My Blog:http://blog.csdn.net/gueter/ 目录:(0)   前言(1) #pragma message能够在编译信息输出窗口中输出相应的信息(2) #pragma code_seg能够设置程序中函数代码存放的代码段,开发驱动程序的时会用到(3) #pragma  once若用在头文件的最开...

任务切换

任务切换 任务切换的方法   第一个方法就是借助中断来进行任务切换,这是现代抢占式多任务的基础。在实模式下,内存最低端1KB是中断向量表,保存着256个中断处理过程的段地址和偏移地址。在保护模式下,处理器不再使用中断向量表,而是使用中断描述符表。中段描述符表和GDT,LDT是一样的,用于保存描述符,唯一不同的地方是,他保存的是门描述符,包括中断门,陷阱门...

DeeplabV3+训练自己的数据集(三)

模型训练及测试 一、在DeepLabv3+模型的基础上,主要需要修改以下两个文件    data_generator.py    train_utils.py    (1)添加数据集描述    在datasets/data_generator.py文件中,添加自己的数据集描述: _CAMVID_INFORMATION = DatasetDescript...

洛谷P3833

Description 树链剖分板子题 考查两种操作 A u v w 把 u 节点到 v 节点路径上所有节点权值加 w Q u 求以 u 为根节点的子树权值之和 首先需要了解线段树和 dfs 序,我这里没有很好的链接,不熟悉的再自行百度吧 另外了解树链剖分的思想(重儿子等等),否则会出很多千奇百怪的错误 树链剖分的构成 DFS1 来处理每个点的...