浅谈算法——树链剖分

前言

首先树链剖分需要使用到线段树知识,不会线段树的童鞋请移步至线段树

在做题中我们会看到一些“在一棵树上进行路径修改、求极值、求和”的题,乍一看能够用线段树解决,其实仅仅凭线段树是根本无法完成的。这时候,我们就需要用到一种看起来高级的复杂算法——树链剖分

基本概念

重儿子:size[u](表示以u为根节点的子树节点个数)为v的子节点中最大的,称u为v的重儿子
轻儿子:v的其他子节点
重边:v与其重儿子的连边
轻边:v与其轻儿子的连边
重链:重边组成的一条链

如图所示,圆圈内为size,粗边为重边,我们称某条路径为重链,当且仅当它全部由重边组成。不存在轻链这一说法

任意一个点到根的路径上,不超过\(\log n\)条轻边,也不超过\(\log n\)条重链,这是整个算法时间复杂度的保证,证明略。因为我也不会证明

基本操作

树链:树上的路径
剖分:把路径分为两类

树链剖分的操作为两次dfs

  • 找重边
  • 把重边连成重链

记size[v]表示以v为根的儿子节点的个数,deep[v]表示v的深度(根深度为1),top[v]表示v所在重链的顶端节点,fa[v]表示v的父亲节点,Rem[v]表示v的重儿子,ID[v]表示v的新编号

第一遍dfs将size,deep,fa,Rem求出来

第二遍dfs的时候,每次优先dfs重儿子,这样可以使得重儿子的编号连续

如下图示,粗边为重边,细边为轻边。节点边带红点的说明其为该条重链的顶端,蓝色数字代表其新编号

为什么要按这种方式去进行树链剖分?
因为在剖分完之后,我们可以发现重链上的点的编号是连续的,所以我们可以用线段树或其他高级数据结构去维护重链上的信息,由于每条重链所占据的编号互不相同,所以我们可以把这些重链首尾相连,放到一个高级数据结构上进行维护。同时我们认为一个点也属于一条重链。轻边的信息可以直接维护。因为它不好维护,除非把边权下移作为点权

树上基本操作

单点修改:直接按新编号对应单点修改即可
路径修改
1、u和v在同一条重链上,直接线段树区间修改即可
2、u和v不在同一条重链上,我们要想办法把它们往同一条重链上靠

  • fa[top[u]]与v在同一条重链上,两次做区间修改即可
  • u经过若干条重链与轻边后和v在同一条重链上,多次做区间修改即可
  • u和v都经过若干条重链与轻边后在同一条重链上,那么每次找到深度较深的点x,做一次区间修改后,在将该点跳到fa[top[x]],知道u和v在同一条重链上

前两种情况是特殊的第三种情况。
查询操作:和路径修改类似,求极值,最大值最小值等,在线段树操作时按照具体情况修改即可

由于经过的重链个数不会超过\(\log n\)级别,因此整个树链剖分的复杂度为\(O(n\log^2n)\),但实际上比某些\(O(n\log n)\)的算法要快

例题

P3384 【模板】树链剖分

Description
如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

Input
第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。
接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x

Output
输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模

Sample Input
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3

Sample Output
2
21

HNIT
对于100%的数据:$ N \leq {10}^5, M \leq {10}^5$

树的结构如下

各个操作如下

故输出应依次为2、21(重要的事情说三遍:记得取模)

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())  if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())    x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
inline void print(int x){
    if (x>=10)     print(x/10);
    putchar(x%10+'0');
}
const int N=1e5;
int v[N+10],dfn[N+10],Line[N+10];
int n,m,root,mod;
struct Segment{//线段树Lazy标记操作
    #define ls (p<<1)
    #define rs ((p<<1)|1)
    int tree[(N<<2)+10],Lazy[(N<<2)+10];
    void updata(int p){tree[p]=(tree[ls]+tree[rs])%mod;}
    void add_tag(int p,int l,int r,int v){
        tree[p]=(tree[p]+1ll*(r-l+1)*v)%mod;
        Lazy[p]=(Lazy[p]+v)%mod;
    }
    void pushdown(int p,int l,int r){
        if (!Lazy[p])   return;
        int mid=(l+r)>>1;
        add_tag(ls,l,mid,Lazy[p]),add_tag(rs,mid+1,r,Lazy[p]);
        Lazy[p]=0;
    }
    void build(int p,int l,int r){
        if (l==r){tree[p]=v[Line[l]]%mod;return;}
        int mid=(l+r)>>1;
        build(ls,l,mid),build(rs,mid+1,r);
        updata(p);
    }
    void insert(int p,int l,int r,int x,int y,int v){
        if (x<=l&&r<=y){add_tag(p,l,r,v);return;}
        int mid=(l+r)>>1;
        pushdown(p,l,r);
        if (x<=mid) insert(ls,l,mid,x,y,v);
        if (y>mid)  insert(rs,mid+1,r,x,y,v);
        updata(p);
    }
    int query(int p,int l,int r,int x,int y){
        if (x<=l&&r<=y) return tree[p];
        pushdown(p,l,r);
        int mid=(l+r)>>1,res=0;
        if (x<=mid) res=(res+query(ls,l,mid,x,y))%mod;
        if (y>mid)  res=(res+query(rs,mid+1,r,x,y))%mod;
        return res;
    }
}Tree;
struct Start{
    int pre[(N<<1)+10],child[(N<<1)+10],now[N+10];
    int deep[N+10],size[N+10],fa[N+10],Rem[N+10],top[N+10];
    int tot,cnt;
    void join(int x,int y){pre[++tot]=now[x],now[x]=tot,child[tot]=y;}
    void build(int x,int Deep){//第一遍dfs
        deep[x]=Deep,size[x]=1;
        for (int p=now[x],son=child[p],Max=0;p;p=pre[p],son=child[p]){
            if (son==fa[x]) continue;
            fa[son]=x;
            build(son,Deep+1);
            size[x]+=size[son];
            if (Max<size[son])  Max=size[son],Rem[x]=son;
        }
    }
    void dfs(int x){//第二遍dfs
        if (!x) return;
        Rem[fa[x]]==x?top[x]=top[fa[x]]:top[x]=x;
        dfn[x]=++cnt,Line[cnt]=x;
        dfs(Rem[x]);
        for (int p=now[x],son=child[p];p;p=pre[p],son=child[p]){
            if (son==fa[x]||son==Rem[x])    continue;
            dfs(son);
        }
    }
    void insert(int x,int y,int z){//区间操作
        while (top[x]!=top[y]){
            if (deep[top[x]]<deep[top[y]])  swap(x,y);
            Tree.insert(1,1,n,dfn[top[x]],dfn[x],z);
            x=fa[top[x]];
        }
        if (deep[x]>deep[y])    swap(x,y);
        Tree.insert(1,1,n,dfn[x],dfn[y],z);
    }
    int query(int x,int y){//区间查询
        int res=0;
        while (top[x]!=top[y]){
            if (deep[top[x]]<deep[top[y]])  swap(x,y);
            res=(res+Tree.query(1,1,n,dfn[top[x]],dfn[x]))%mod;
            x=fa[top[x]];
        }
        if (deep[x]>deep[y])    swap(x,y);
        res=(res+Tree.query(1,1,n,dfn[x],dfn[y]))%mod;
        return res;
    }
}T;
int main(){
    n=read(),m=read(),root=read(),mod=read();
    for (int i=1;i<=n;i++)  v[i]=read();
    for (int i=1,x,y;i<n;i++)   x=read(),y=read(),T.join(x,y),T.join(y,x);
    T.build(root,1),T.dfs(root),Tree.build(1,1,n);
    for (int i=1;i<=m;i++){
        int t=read();
        if (t==1){
            int x=read(),y=read(),z=read();
            T.insert(x,y,z);
        }
        if (t==2){
            int x=read(),y=read();
            printf("%d\n",T.query(x,y));
        }
        if (t==3){//子树修改
            int x=read(),z=read();
            Tree.insert(1,1,n,dfn[x],dfn[x]+T.size[x]-1,z);
        }
        if (t==4){//子树查询
            int x=read();
            printf("%d\n",Tree.query(1,1,n,dfn[x],dfn[x]+T.size[x]-1));
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Wolfycz/p/9459332.html