树链剖分 笔记

博客介绍了用DFS序解决树上子树/单点加权、求和问题,当遇到链/单点修改、查询时,原方法失效。提出树链剖分方法,通过合理分块并用线段树维护,还介绍了优化分法及复杂度,最后说明了实现步骤和给出练手题目。

当我们遇到一些树上的问题时,我们珂能会用 D F S DFS DFS序解决子树/单点加权,子树/单点求和的问题。如果遇到了链求和,我们也珂以把 D F S DFS DFS序存两次,用正负系数抵消。(见这篇博客)

那么,如果链/单点修改,链/单点查询呢?

完了,刚刚那个方法不管用了。。。因为链不一定 D F S DFS DFS序连续,就搞不了了。但是,我们珂以想一个方法使得它连续。如果有想过自己脑补的同学,肯定放弃过这样一个想法:下图是一个树
blog1.jpg
然后我们对其进行如下划分(如蓝色所示):
blog2.jpg
然后按每条链遍历的顺序编号(此处已经如此编号了)。当我们对链进行修改/查询时,只要分块一下,用线段树维护即珂。
那为什么放弃呢?

  1. 不好实现(千万不要怕这个,我今天刚学,代码写了300行,只要能够坚持,能调出来的,相信我)
  2. 慢(这点确实,看后面优化)

如上面所说,这个真的很慢。我们想想,上面这个还好,如果数据成这样:
blog3.jpg
然后我们按蓝色的分了。这样就分出来了 50001 50001 50001块,如果每次都从 100000 100000 100000改到 1 1 1,那么不是每次都 O ( n 2 ) = O ( n ) O(\frac{n}{2})=O(n) O(2n)=O(n)了,询问多点,就 O ( n m ) O(nm) O(nm)了。显然,这个在多数问题中过不去(模板也过不去)。找一个河(hè)南(nan)的同学,去听ta念 n 2 n^2 n2,会念成én fàng

如何优化这个分法呢?

  1. 随机分(这个要拼运气,所以还是找个稳妥的方法)
  2. 去找子节点最多的先分(看起来不错)

那么效率谁好呢?1显然是瞎搞,但是2,被证明过,无论你 u , v u,v u,v怎么取, u u u v v v之间路径上被分出来的块数是 l o g n logn logn级别的。这样就珂以 O ( l o g n ∗ T ∗ m ) O(logn*T*m) O(lognTm)完成了,其中 T T T是数据结构的复杂度(如线段树的 T T T值是 l o g n logn logn), m m m是询问数。如果 m , n m,n m,n同级,这样分就是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),一般能过 n &lt; = 1 0 5 n&lt;=10^5 n<=105。( n &lt; = 1 0 6 n&lt;=10^6 n<=106,要么是大力卡常,要么想别的办法)。

然后块分好了。这样就珂以按分出来的块存好 D F S DFS DFS序,而且要记录好每个块的最上面的点 t o p [ ] top[] top[]。这样,树链剖分就十分的强大,不仅优秀的继承了 D F S DFS DFS序连续的特性,还能搞链,就珂以进行子树/单点/链 修改/求和(乘一下也就是 6 6 6种操作)。其中子树/单点的修改/求和照样是 O ( l o g n ) O(logn) O(logn),链修改/求和是 O ( l o g 2 n ) O(log^2n) O(log2n)

具体讲一下如何实现链的修改/求和(从点 u u u v v v):
由于 u , v u,v u,v对称,不妨设 t o p [ u ] top[u] top[u]的深度 &gt; = t o p [ v ] &gt;=top[v] >=top[v]的深度(即 u u u要往 v v v上跳),然后:

  1. t o p [ u ] top[u] top[u] u u u进行求和/修改
  2. u u u跳到 t o p [ u ] top[u] top[u]上面(即 u = f a [ t o p [ u ] ] u=fa[top[u]] u=fa[top[u]]),回到第一步,直到 u , v u,v u,v在同一个链里,进入第3步
  3. 此时 u , v u,v u,v同链,直接求和/修改即可。

那么,如何写树链剖分呢?具体讲一下步骤:

  1. 存图,读入点权(或者初始都为 0 0 0
  2. 第一次 D F S DFS DFS处理出每个点的:子树大小,子树大小最大的儿子,深度,父亲,
  3. 第二次 D F S DFS DFS按照先最大儿子再别的点的顺序,处理出: D F S DFS DFS序,每个链最顶上的点编号,以及按照 D F S DFS DFS序构建的点权。
  4. 按照新的点权建线段树(此时注意,只有新的点权和线段树是拿 D F S DFS DFS序编号的,其余都是按原序编号的)
  5. 此时就珂以开开心心的进行操作啦 Q ω Q QωQ QωQ

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 200100
    #define mod p
    class Graph//非要面向对象存图的我
    //导致代码长的罪魁祸首之一:面向对象
    {
        public:
            int head[N];
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }Ed[N];

            void clear()
            {
                memset(Ed,-1,sizeof(Ed));
                memset(head,-1,sizeof(head));
                EdgeCount=0;
            }
            void AddEdge(int u,int v,int w)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }

            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }
    }G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}

    int n,m,r,p;
    int w[N];
    void R1(int &x)
    {
        x=0;int f=1;char c=getchar();
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==-1)?-x:x;
    }
    void Rarr(int p[N],int len)
    {
        for(int i=1;i<=len;++i)
        {
            R1(p[i]);
        }
    }//罪魁祸首之二:快读
    void Input()
    {
        R1(n),R1(m),R1(r),R1(p);
        Rarr(w,n);
        for(int i=1;i<n;++i)
        {
            int a,b;
            R1(a),R1(b);
            Add(a,b,1);
        }
    }

    bool upmax(int &x,int y) {return (y>x)?(x=y,1):0;}
    bool upmin(int &x,int y) {return (y<x)?(x=y,1):0;}
    int deep[N],fa[N],size[N],son[N];
    void DFS1(int u,int f)
    {
        deep[u]=(u==r)?1:deep[f]+1;
        fa[u]=f;
        size[u]=1;//处理这些很水的值

        son[u]=-1;int Max=-1;
        if (G.Start(u)==-1) return;
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=f)
            {
                DFS1(v,u);
                size[u]+=size[v];
                if (upmax(Max,size[v])) son[u]=v;//更新最大的儿子
            }
        }
    }
    int DFSid[N];int cnt=0;
    int wt[N];
    int top[N];
    void DFS2(int u,int topu)
    {
        DFSid[u]=++cnt;
        wt[cnt]=w[u];
        top[u]=topu;
        if (son[u]==-1) return;

        DFS2(son[u],topu);//最大的儿子直接继承当前的链
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=fa[u] and v!=son[u] and ~v)
            {
                DFS2(v,v);//别的儿子新开始一条链
            }
        }
    }

    class SegmentTree//线段树(区间加,区间和)
    {
        public:
            struct node
            {
                int l,r;
                int s,a;
            }tree[N<<2];
            #define ls index<<1
            #define rs index<<1|1

            #define L tree[index].l
            #define R tree[index].r
            #define S tree[index].s
            #define A tree[index].a

            #define lL tree[ls].l
            #define lR tree[ls].r
            #define lS tree[ls].s
            #define lA tree[ls].a

            #define rL tree[rs].l
            #define rR tree[rs].r
            #define rS tree[rs].s
            #define rA tree[rs].a
            void Update(int index)
            {
                S=(lS+rS)%mod;
            }
            void BuildTree(int l,int r,int index)
            {
                L=l,R=r,S=A=0;
                if (l==r)
                {
                    S=wt[l]%mod;
                    return;
                }
                int mid=(l+r)>>1;
                BuildTree(l,mid,ls);
                BuildTree(mid+1,r,rs);
                Update(index);
            }
            void AddOne(int x,int index)
            {
                S+=x*(R-L+1);S%=mod;
                A+=x;A%=mod;
            }
            void PushDown(int index)
            {
                if (A)
                {
                    AddOne(A,ls);
                    AddOne(A,rs);
                    A=0;
                }
            }
            void Add(int l,int r,int x,int index)
            {
                if (l>r) return;
                if (l>R or L>r) return;
                if (l<=L and R<=r) return AddOne(x,index);
                PushDown(index);
                Add(l,r,x,ls);
                Add(l,r,x,rs);
                Update(index);
            }
            int Query(int l,int r,int index)
            {
                if (l>r) return 0;
                if (l>R or L>r) return 0;
                if (l<=L and R<=r) return S%mod;
                PushDown(index);
                return (Query(l,r,ls)+Query(l,r,rs))%mod;
            }
            #undef ls //index<<1
            #undef rs //index<<1|1

            #undef L //tree[index].l
            #undef R //tree[index].r
            #undef S //tree[index].s
            #undef A //tree[index].a

            #undef lL //tree[ls].l
            #undef lR //tree[ls].r
            #undef lS //tree[ls].s
            #undef lA //tree[ls].a

            #undef rL //tree[rs].l
            #undef rR //tree[rs].r
            #undef rS //tree[rs].s
            #undef rA //tree[rs].a
    }T;
    void BuildCut()//初始化树链剖分
    {
        DFS1(r,-1);
        DFS2(r,-1);
        T.BuildTree(1,n,1);
    }
    void PathAdd(int u,int v,int x)//路径加
    {
        x%=mod;
        while(top[u]!=top[v])//不在一条链上
        {
            if (deep[top[u]]<deep[top[v]]) swap(u,v);//设top[u]>=top[v]
            T.Add(DFSid[top[u]],DFSid[u],x,1);//从top[u]到u
            //注意线段树上要以DFS序编号
            u=fa[top[u]];//跳上去
        }
        if (deep[u]>deep[v]) swap(u,v);
        T.Add(DFSid[u],DFSid[v],x,1);//在一个链上的处理
    }
    int PathQuery(int u,int v)//路径求和,和上面差不多
    {
        int ans=0;
        while(top[u]!=top[v])
        {
            if (deep[top[u]]<deep[top[v]]) swap(u,v);
            ans+=T.Query(DFSid[top[u]],DFSid[u],1);ans%=mod;
            u=fa[top[u]];
        }
        if (deep[u]>deep[v]) swap(u,v);
        ans+=T.Query(DFSid[u],DFSid[v],1);ans%=mod;
        return ans;
    }
    void SubTAdd(int u,int x)//子树加
    {
        x%=mod;
        T.Add(DFSid[u],DFSid[u]+size[u]-1,x,1);
    }
    int SubTQuery(int u)//子树求和
    {
        return T.Query(DFSid[u],DFSid[u]+size[u]-1,1)%mod;
    }

    void Query()
    {
        for(int i=1;i<=m;++i)
        {
            int o;scanf("%d",&o);
            if (o==1)
            {
                int u,v,x;
                scanf("%d%d%d",&u,&v,&x);
                PathAdd(u,v,x);
            }
            else if (o==2)
            {
                int u,v;
                scanf("%d%d",&u,&v);
                printf("%d\n",PathQuery(u,v));
            }
            else if (o==3)
            {
                int u,x;
                scanf("%d%d",&u,&x);
                SubTAdd(u,x);
            }
            else
            {
                int u;
                scanf("%d",&u);
                printf("%d\n",SubTQuery(u));
            }
        }
    }//处理询问操作
    void Main()
    {
        G.clear();
        Input();

        BuildCut();
        Query();
    }
    #undef N //200100
    #undef mod //p
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

听完我瞎扯,差不多会树剖了⑧。。。
但是,多加练习还是必不珂少的。。。
几个练手的板子题:
洛谷上的模板
浙江省选
HDU爽题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值