首页 > 其他 > 详细

树链剖分学习笔记

时间:2019-02-24 22:31:58      阅读:241      评论:0      收藏:0      [点我收藏+]

基础知识详见这篇:banananana:树链剖分原理和实现

请务必看完!写的很棒,学习了!


 

根据上面博客的讲解,树链剖分会得到几个很重要的数组

第一次dfs得到:

\[fa[i],dep[i],sz[i],son[i]\]

分别表示 父亲节点、深度、子树大小、重节点

 第二次dfs得到:

\[id[i],rnk[i],top[i]\]

分别表示 节点序号对应的dfs序、dfs序对应的节点序号、链的顶端(若此节点为重节点,则为重链的顶端;若为轻节点,则为自身)

这里的dfs序大有讲究:由于在第二次dfs保证优先对重节点进行dfs,所以一条重链上所有节点的dfs序相邻(性质1)

同时,树链剖分保证:对于任意一个节点,若想回到原点,最多只需经过logN条链(性质2)

通过这两条性质,如果我们想对树上的一条路径进行某些计算,我们最多只需要枚举O(logN)级别的数量的链;而由于链的dfs序相邻,我们可以使用数据结构帮助我们高效完成计算

 

最基本的应用是求LCA

虽然用倍增比较方便,但是写一写树剖的实现有助于加深理解

比较让人担心的一种情况是,LCA恰好在一条重链上,同时x、y其中有点需要经过这条链

我们有一种办法可以避免“跳过头”,就是比较两个点向上跳一条链后的深度,选择跳一条链后较深的进行跳转

技术分享图片

而找到LCA的条件是:x、y位于同一条重链上(即top[x]==top[y]),那么LCA为深度较低的点

LCA模板题:洛谷P3379(【模板】最近公共祖先)

(用vector存边就乖乖开O2吧)

技术分享图片
#include <cstring>
#include <cmath>
#include <cstdio>
#include <vector>
using namespace std;

const int N=500005;

int n,m,root;
vector<int> v[N];

int fa[N],dep[N],sz[N],son[N];

inline void dfs1(int x,int f)
{
    sz[x]=1;
    fa[x]=f;
    dep[x]=dep[f]+1;
    
    for(int i=0;i<v[x].size();i++)
    {
        int next=v[x][i];
        if(next==f)
            continue;
        
        dfs1(next,x);
        sz[x]+=sz[next];
        if(!son[x] || sz[son[x]]<sz[next])
            son[x]=next;
    }
}

int cnt;
int id[N],rnk[N],top[N];

inline void dfs2(int x,int t)
{
    top[x]=t;
    id[x]=++cnt;
    rnk[cnt]=x;
    
    if(son[x])
        dfs2(son[x],t);
    
    for(int i=0;i<v[x].size();i++)
    {
        int next=v[x][i];
        if(next==fa[x] || next==son[x])
            continue;
        
        dfs2(next,next);
    }
}

inline int lca(int x,int y)
{
    while(top[x]!=top[y])
        if(dep[top[x]]>dep[top[y]])
            x=fa[top[x]];
        else
            y=fa[top[y]];
    
    return (dep[x]<dep[y]?x:y);
}

int main()
{
//    freopen("input.txt","r",stdin);
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    
    dfs1(root,0);
    dfs2(root,root);
    
    while(m--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}
View Code

 

一道模板题:洛谷P3384 (【模板】树链剖分)

求和利用的是区间和线段树,修改用懒标记即可

操作1、2:注意利用两条性质,对于每条链分开处理;单次复杂度O((logN)2)

操作3、4:多记录一个离开节点i的dfs序(按理说这个应该更基础点吧);单次复杂度O(logN)

技术分享图片
#include <cstring>
#include <cmath>
#include <cstdio>
#include <vector>
using namespace std;

typedef long long ll;
const int N=100005;

int n,m,root,P,size=1;
vector<int> v[N];

int fa[N],dep[N],sz[N],son[N];

inline void dfs1(int x,int f)
{
    sz[x]=1;
    fa[x]=f;
    dep[x]=dep[f]+1;
    
    for(int i=0;i<v[x].size();i++)
    {
        int next=v[x][i];
        if(next==f)
            continue;
        
        dfs1(next,x);
        sz[x]+=sz[next];
        if(!son[x] || sz[son[x]]<sz[next])
            son[x]=next;
    }
}

int cnt;
int id[N],rid[N],rnk[N],top[N];

inline void dfs2(int x,int t)
{
    top[x]=t;
    id[x]=++cnt;
    rnk[cnt]=x;
    
    if(son[x])
        dfs2(son[x],t);
    
    for(int i=0;i<v[x].size();i++)
    {
        int next=v[x][i];
        if(next==fa[x] || next==son[x])
            continue;
        
        dfs2(next,next);
    }
    
    rid[x]=cnt;
}

int val[N];
int t[N<<2],len[N<<2],tag[N<<2];

inline void Build(int k,int l,int r,int a,int b)
{
    if(a>r)
        return;
    if(a==b)
    {
        t[k]=val[rnk[a]];//***
        len[k]=1;
        return;
    }
    
    int mid=(a+b)>>1;
    Build(k<<1,l,r,a,mid);
    Build(k<<1|1,l,r,mid+1,b);
    
    t[k]=(t[k<<1]+t[k<<1|1])%P;
    len[k]=len[k<<1]+len[k<<1|1];
}

inline void Update(int k)
{
    if(!tag[k])
        return;
    
    tag[k<<1]=(tag[k<<1]+tag[k])%P;
    t[k<<1]=(t[k<<1]+(ll)len[k<<1]*tag[k])%P;
    tag[k<<1|1]=(tag[k<<1|1]+tag[k])%P;
    t[k<<1|1]=(t[k<<1|1]+(ll)len[k<<1|1]*tag[k])%P;
    tag[k]=0;
}

inline void Modify(int k,int l,int r,int a,int b,int x)
{
    if(a>r || b<l)
        return;
    if(a>=l && b<=r)
    {
        tag[k]=(tag[k]+x)%P;
        t[k]=(t[k]+(ll)len[k]*x)%P;
        return;
    }
    
    Update(k);
    int mid=(a+b)>>1;
    Modify(k<<1,l,r,a,mid,x);
    Modify(k<<1|1,l,r,mid+1,b,x);
    t[k]=(t[k<<1]+t[k<<1|1])%P;
}

inline int Query(int k,int l,int r,int a,int b)
{
    if(a>r || b<l)
        return 0;
    if(a>=l && b<=r)
        return t[k];
    
    Update(k);
    int mid=(a+b)>>1;
    return (Query(k<<1,l,r,a,mid)+Query(k<<1|1,l,r,mid+1,b))%P;
}

int main()
{
//    freopen("input.txt","r",stdin);
    scanf("%d%d%d%d",&n,&m,&root,&P);
    for(int i=1;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    
    dfs1(root,0);
    dfs2(root,root);
    
    while(size<n)
        size<<=1;
    Build(1,1,n,1,size);
    
    while(m--)
    {
        int op,x,y,z,ans=0;
        scanf("%d",&op);
        
        if(op==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            while(top[x]!=top[y])
                if(dep[top[x]]>dep[top[y]])
                {
                    Modify(1,id[top[x]],id[x],1,size,z);
                    x=fa[top[x]];
                }
                else
                {
                    Modify(1,id[top[y]],id[y],1,size,z);
                    y=fa[top[y]];
                }
            
            if(dep[x]>dep[y])
                swap(x,y);
            Modify(1,id[x],id[y],1,size,z);
        }
        if(op==2)
        {
            scanf("%d%d",&x,&y);
            while(top[x]!=top[y])
                if(dep[top[x]]>dep[top[y]])
                {
                    ans=(ans+Query(1,id[top[x]],id[x],1,size))%P;
                    x=fa[top[x]];
                }
                else
                {
                    ans=(ans+Query(1,id[top[y]],id[y],1,size))%P;
                    y=fa[top[y]];
                }
            
            if(dep[x]>dep[y])
                swap(x,y);
            ans=(ans+Query(1,id[x],id[y],1,size))%P;
            printf("%d\n",ans);
        }
        if(op==3)
        {
            scanf("%d%d",&x,&z);
            Modify(1,id[x],rid[x],1,size,z);
        }
        if(op==4)
        {
            scanf("%d",&x);
            printf("%d\n",Query(1,id[x],rid[x],1,size));
        }
    }
    return 0;
}
View Code

 

(待续)

树链剖分学习笔记

原文:https://www.cnblogs.com/LiuRunky/p/Tree_Spliting.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!