树链剖分

Posted orangee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树链剖分相关的知识,希望对你有一定的参考价值。

树链剖分

树剖是将树的节点分为轻重点,再将边分为轻重链,然后用树状数组、线段树等数据结构进行维护的算法思想。个人认为本质上可以看作线段树等维护区间的数据结构在树上的推广应用。换句话说,就是将树hash成几段连续区间以便数据结构来维护。

适用问题

对于一棵有点权的树

  • 对两点间最短路径上点的修改与询问操作
  • 对于根为x的子树的修改与询问操作

原理

网上有很多讲解,都讲的比较清楚,如树链剖分原理与实现,大意就是将树拆分成链来维护。

实现

大致由两个DFS与修改、查询函数组成。

两次DFS维护出一下几个东西

  • son[u]节点u的重儿子
  • top[u]节点u所在重链的顶点(不在重链上就是自己本身)
  • sz[u]以节点u为根的子树的大小
  • fa[u]节点u的父亲
  • dep[u]节点u所处深度
  • id[u]节点u的新编号
  • rk[i]编号为i的节点
void dfs1(int fa,int u,int deep)
{
    f[u]=fa;
    dep[u]=deep;
    sz[u]=1;
    for (int i=head[u];i;i=edge[i].ne)
    {
        int v=edge[i].v;
        if (v==fa)
            continue;
        dfs1(u,v,deep+1);
        sz[u]+=sz[v];
        if (sz[v]>sz[son[u]])
            son[u]=v;
    }
}
void dfs2(int u,int t)
{
    top[u]=t;
    id[u]=++tot;
    rk[tot]=u;
    if (!son[u])
        return;
    dfs2(son[u],t);
    for (int i=head[u];i;i=edge[i].ne)
    {
        int v=edge[i].v;
        if (v!=f[u]&&v!=son[u])
          dfs2(v,v);
    }
}

至于查询与修改操作怎么用到以上维护出的东西,可以参考下面两道模板题的写法。大概思想就是,对于路径操作,根据维护出的链来快速地把两点移到同一条重链上(沿途要处理修改或者答案);对于子树操作,不难发现子树的节点是一段连续区间,可以直接用线段树来维护。
洛谷上有两道裸题,可以用来熟悉树剖的基本操作与思想。

P3178

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<cmath>
#include<climits>
#include<functional>
#include<set>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<endl
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef vector<int> V;
typedef map<int,int> M;
typedef queue<int> Q;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=2e5+10,INF=0x3f3f3f3f;
int cnt,head[maxn];
struct Edge
{
    int v,ne;
}edge[maxn<<1];
void add(int u,int v)
{
    edge[++cnt].ne=head[u];
    edge[cnt].v=v;
    head[u]=cnt;
}
int root=1,tot,son[maxn],id[maxn],rk[maxn],sz[maxn],f[maxn],dep[maxn],top[maxn];
void dfs1(int fa,int u,int deep)
{
    f[u]=fa;
    dep[u]=deep;
    sz[u]=1;
    for (int i=head[u];i;i=edge[i].ne)
    {
        int v=edge[i].v;
        if (v==fa)
            continue;
        dfs1(u,v,deep+1);
        sz[u]+=sz[v];
        if (sz[v]>sz[son[u]])
            son[u]=v;
    }
}
void dfs2(int u,int t)
{
    top[u]=t;
    id[u]=++tot;
    rk[tot]=u;
    if (!son[u])
        return;
    dfs2(son[u],t);
    for (int i=head[u];i;i=edge[i].ne)
    {
        int v=edge[i].v;
        if (v!=f[u]&&v!=son[u])
          dfs2(v,v);
    }
}
int g[maxn];
ll sum[maxn<<2],lazy[maxn<<2];
void build(int l,int r,int rt)
{
    if (l==r)
    {
        sum[rt]=g[rk[l]];
        return;
    }
    int m=(l+r)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void upd(int L,int R,ll c,int l,int r,int rt)
{
    sum[rt]+=c*(R-L+1);
    if (L==l&&R==r)
    {
        lazy[rt]+=c;
        return;
    }
    int m=(l+r)>>1;
    if (R<=m)
        upd(L,R,c,l,m,rt<<1);
    else if (L>m)
        upd(L,R,c,m+1,r,rt<<1|1);
    else
        upd(L,m,c,l,m,rt<<1),upd(m+1,R,c,m+1,r,rt<<1|1);
}
ll qry(int L,int R,ll add,int l,int r,int rt)
{
    if (L==l&&R==r)
        return sum[rt]+add*(R-L+1);
    int m=(l+r)>>1;
    if (R<=m)
        return qry(L,R,add+lazy[rt],l,m,rt<<1);
    else if (L>m)
        return qry(L,R,add+lazy[rt],m+1,r,rt<<1|1);
    else
        return qry(L,m,add+lazy[rt],l,m,rt<<1)+qry(m+1,R,add+lazy[rt],m+1,r,rt<<1|1);
}
ll qry_path(int x,int y)
{
    ll res=0;
    int fx=top[x],fy=top[y];
    while (fx!=fy)
    {
        if (dep[fx]>dep[fy])
        {
            res+=qry(id[fx],id[x],0,1,tot,1);
            x=f[fx];
        }
        else
        {
            res+=qry(id[fy],id[y],0,1,tot,1);
            y=f[fy];
        }
        fx=top[x],fy=top[y];
    }
    if (id[x]>id[y])
        res+=qry(id[y],id[x],0,1,tot,1);
    else
        res+=qry(id[x],id[y],0,1,tot,1);
    return res;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
        scanf("%d",&g[i]);
    for (int i=0;i<n-1;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs1(0,root,1);
    dfs2(root,root);
    build(1,tot,1);
    while (m--)
    {
        int op,x,z;
        scanf("%d",&op);
        if (op==1)
        {
            scanf("%d%d",&x,&z);
            upd(id[x],id[x],z,1,tot,1);
        }
        else if (op==2)
        {
            scanf("%d%d",&x,&z);
            upd(id[x],id[x]+sz[x]-1,z,1,tot,1);
        }
        else
        {
            scanf("%d",&x);
            printf("%lld
",qry_path(x,root));
        }
    }
    return 0;
}

P3384

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<string>
#include<vector>
#include<cmath>
#include<climits>
#include<functional>
#include<set>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<endl
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef vector<int> V;
typedef map<int,int> M;
typedef queue<int> Q;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=2e5+10,INF=0x3f3f3f3f;
int cnt,head[maxn];
struct Edge
{
    int v,ne;
}edge[maxn<<1];
void add(int u,int v)
{
    edge[++cnt].ne=head[u];
    edge[cnt].v=v;
    head[u]=cnt;
}
int root,p,tot,son[maxn],id[maxn],rk[maxn],top[maxn],sz[maxn],dep[maxn],f[maxn];
inline int pp(ll a,ll b)
{
    a+=b;
    if (a>=p)
        a-=p;
    return a;
}
inline int mul(ll a,ll b)
{
    return a*b%p;
}
void dfs1(int fa,int u,int deep)
{
    f[u]=fa;
    dep[u]=deep;
    sz[u]=1;
    for (int i=head[u];i;i=edge[i].ne)
    {
        int v=edge[i].v;
        if (v==fa)
            continue;
        dfs1(u,v,deep+1);
        sz[u]+=sz[v];
        if (sz[v]>sz[son[u]])
            son[u]=v;
    }
}
void dfs2(int u,int t)
{
    top[u]=t;
    id[u]=++tot;
    rk[tot]=u;
    if (!son[u])
        return;
    dfs2(son[u],t);
    for (int i=head[u];i;i=edge[i].ne)
    {
        int v=edge[i].v;
        if (v!=f[u]&&v!=son[u])
            dfs2(v,v);
    }
}
int g[maxn],sum[maxn<<2],lazy[maxn<<2];
void build(int l,int r,int rt)
{
    if (l==r)
    {
        sum[rt]=g[rk[l]];
        return;
    }
    int m=(l+r)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    sum[rt]=pp(sum[rt<<1],sum[rt<<1|1]);
}
void upd(int L,int R,int c,int l,int r,int rt)
{
    sum[rt]=pp(sum[rt],mul(c,R-L+1));
    if (L==l&&R==r)
    {
        lazy[rt]=pp(lazy[rt],c);
        return;
    }
    int m=(l+r)>>1;
    if (R<=m)
        upd(L,R,c,l,m,rt<<1);
    else if (L>m)
        upd(L,R,c,m+1,r,rt<<1|1);
    else
        upd(L,m,c,l,m,rt<<1),upd(m+1,R,c,m+1,r,rt<<1|1);
}
int qry(int L,int R,int add,int l,int r,int rt)
{
    if (L==l&&R==r)
        return pp(sum[rt],mul(add,R-L+1));
    int m=(l+r)>>1;
    if (R<=m)
        return qry(L,R,pp(add,lazy[rt]),l,m,rt<<1);
    else if (L>m)
        return qry(L,R,pp(add,lazy[rt]),m+1,r,rt<<1|1);
    else
        return pp(qry(L,m,pp(add,lazy[rt]),l,m,rt<<1),qry(m+1,R,pp(add,lazy[rt]),m+1,r,rt<<1|1));
}
void upd_path(int x,int y,int z)
{
    int fx=top[x],fy=top[y];
    while (fx!=fy)
    {
        if (dep[fx]>dep[fy])
        {
            upd(id[fx],id[x],z,1,tot,1);
            x=f[fx];
        }
        else
        {
            upd(id[fy],id[y],z,1,tot,1);
            y=f[fy];
        }
        fx=top[x],fy=top[y];
    }
    if (id[x]>id[y])
        upd(id[y],id[x],z,1,tot,1);
    else
        upd(id[x],id[y],z,1,tot,1);
}
int qry_path(int x,int y)
{
    int res=0;
    int fx=top[x],fy=top[y];
    while (fx!=fy)
    {
        if (dep[fx]>dep[fy])
        {
            res=pp(res,qry(id[fx],id[x],0,1,tot,1));
            x=f[fx];
        }
        else
        {
            res=pp(res,qry(id[fy],id[y],0,1,tot,1));
            y=f[fy];
        }
        fx=top[x],fy=top[y];
    }
    if (id[x]>id[y])
        res=pp(res,qry(id[y],id[x],0,1,tot,1));
    else 
        res=pp(res,qry(id[x],id[y],0,1,tot,1));
    return res;
}
int main()
{
    int n,m;
    scanf("%d%d%d%d",&n,&m,&root,&p);
    for (int i=1;i<=n;++i)
        scanf("%d",&g[i]);
    for (int i=0;i<n-1;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs1(0,root,1);
    dfs2(root,root);
    build(1,tot,1);
    while (m--)
    {
        int op,x,y,z;
        scanf("%d",&op);
        if (op==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            upd_path(x,y,z);
        }
        else if (op==2)
        {
            scanf("%d%d",&x,&y);
            printf("%d
",qry_path(x,y));
        }
        else if (op==3)
        {
            scanf("%d%d",&x,&z);
            upd(id[x],id[x]+sz[x]-1,z,1,tot,1);
        }
        else
        {
            scanf("%d",&x);
            printf("%d
",qry(id[x],id[x]+sz[x]-1,0,1,tot,1));
        }
    }
    return 0;
}

以上是关于树链剖分的主要内容,如果未能解决你的问题,请参考以下文章

树链剖分小结

树链剖分详解

树链剖分

树链剖分 入门

树链剖分

树链剖分(轻/重链剖分学习笔记)