树链剖分

Posted *Miracle*

tags:

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

意义:

树链剖分 就是对一棵树分成几条链,把树形变为线性,减少处理难度

概念

重儿子:对于每一个非叶子节点,它的儿子中 儿子数量最多的那一个儿子 为该节点的重儿子
轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子
叶子节点没有重儿子也没有轻儿子(因为它没有儿子。。)
重边:连接任意两个重儿子的边叫做重边
轻边:剩下的即为轻边
重链:相邻重边连起来的 连接一条重儿子 的链叫重链
对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链
每一条重链以轻儿子为起点

题目大意:

给定一棵有根树,给定每个点初值。 需要处理的问题:

将树从x到y结点最短路径上所有节点的值都加上z
求树从x到y结点最短路径上所有节点的值之和
将以x为根节点的子树内所有节点值都加上z
求以x为根节点的子树内所有节点值之和

分析:

树链剖分+线段树

树剖部分:

需要数组:

int root,n,m,p;
int dfn[N],dfn2[N],fdfn[N];
int top[N],son[N],fa[N],dep[N],size[N];

1.dfs1:

目标:

①找到fa,重儿子(son)

②处理节点深度,子树大小(size)(dep[root]=1,fa[root]=-1,其实本题不固定)

void dfs1(int x,int f,int d)
{
    dep[x]=d;
    size[x]=1;
    int mx=0;
    for(int i=head[x];i;i=bian[i].nxt)
    {
        int y=bian[i].to;
        if(y==f) continue;//不能回走
        fa[y]=x;
        dfs1(y,x,d+1);
        size[x]+=size[y];
        if(size[y]>mx)
        {
            mx=size[y],son[x]=y;//记录重儿子
        }
    }
}

2.dfs2

目标:

①找到dfn,dfn2(子树结尾dfn)便于之后线段树维护区间。

②处理fdfn,记录dfnx是几号点。便于线段树build

③注意:有重儿子,先走重儿子。

结果:

dfn数组中,一棵完整的子树,其dfn也是连续的一段。每条重链也是连续的一段。这样,用线段树很方便维护树上路径的处理。

void dfs2(int x,int f)
{
    dfn[x]=++tot;
    fdfn[tot]=x;//第tot个dfn是x号
    if(!top[x]) top[x]=x;//top未赋值才能赋值
    if(son[x]) top[son[x]]=top[x],dfs2(son[x],x);//先走重儿子
    for(int i=head[x];i;i=bian[i].nxt)
    {
        int y=bian[i].to;
        if(y==son[x]||y==f) continue;
        dfs2(y,x);
    }
    dfn2[x]=tot;//回溯之前记录下子树结尾dfn
}

此处省去线段树常规操作,详见下面代码。

3.work1

利用树剖lca想法,其中一个点一边向上翻的同时,更新值。最后在同一条链上了之后,相当于已经找到了lca直接更新另一条路径。

void work1(int x,int y,int z)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]]) swap(x,y);//dep[top]深度深的向上翻
        add(1,1,tot,dfn[top[y]],dfn[y],z);
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    add(1,1,tot,dfn[x],dfn[y],z);//另一边路径
}

work2同理。

4.work3,work4,利用之前记录过的dfn2,可以直接找到子树区间。直接处理即可。

void work3(int x,int z)
{
    add(1,1,tot,dfn[x],dfn2[x],z);
}
int work4(int x)
{
    int sum=0;
    sum=(sum+query(1,1,tot,dfn[x],dfn2[x]))%p;
    return sum;
}

注意事项:

1.每次dfs注意不要返祖。

2.记得取模!!!任何加减,赋值,求和都要提起注意。

3.区间add标记直接加,sum要+c×(len)必须乘区间!!(线段树不过关。。。)

4.root是原来树的根,线段树的根就是1!!(不要混了)RE无数无数无数

详见代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int root,n,m,p;
int dfn[N],dfn2[N],fdfn[N];
int top[N],son[N],fa[N],dep[N],size[N];
struct node{
    int nxt,to;
}bian[2*N];
int cnt,tot;
int head[N];
void add(int x,int y)
{
    bian[++cnt].nxt=head[x];
    bian[cnt].to=y;
    head[x]=cnt;
}
void dfs1(int x,int f,int d)
{
    dep[x]=d;
    size[x]=1;
    int mx=0;
    for(int i=head[x];i;i=bian[i].nxt)
    {
        int y=bian[i].to;
        if(y==f) continue;
        fa[y]=x;
        dfs1(y,x,d+1);
        size[x]+=size[y];
        if(size[y]>mx)
        {
            mx=size[y],son[x]=y;
        }
    }
}
void dfs2(int x,int f)
{
    dfn[x]=++tot;
    fdfn[tot]=x;
    if(!top[x]) top[x]=x;
    if(son[x]) top[son[x]]=top[x],dfs2(son[x],x);
    for(int i=head[x];i;i=bian[i].nxt)
    {
        int y=bian[i].to;
        if(y==son[x]||y==f) continue;
        dfs2(y,x);
    }
    dfn2[x]=tot;
}
//-------------------以上树剖 ----------------------------------- 
int mod(int x)
{
    while(x>=p) x-=p;
    while(x<0) x+=p;
    return x;
}
struct tree{
    int sum,add;
    #define s(x) t[x].sum
    #define ad(x) t[x].add 
}t[4*N];
void pushup(int x)
{
    s(x)=mod(s(x<<1)+s(x<<1|1));
}
void build(int x,int l,int r)
{
    if(l==r)
    {
        s(x)=mod(a[fdfn[l]]);ad(x)=0;
        return;
    }
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    pushup(x);
}
void pushdown(int x,int l,int r)//change sum+=ad*len
{
    int s1=x<<1,s2=x<<1|1;
    int mid=(l+r)>>1;
    ad(s1)=mod(ad(s1)+ad(x));
    s(s1)=mod(s(s1)+ad(x)*(mid-l+1));
    ad(s2)=mod(ad(s2)+ad(x));
    s(s2)=mod(s(s2)+ad(x)*(r-mid));
    ad(x)=0;
}
void add(int x,int l,int r,int L,int R,int c)
{
    if(L<=l&&r<=R)
    {
        s(x)=mod(s(x)+mod(c*(r-l+1)));
        ad(x)=mod(ad(x)+c);
        return;
    }
    pushdown(x,l,r);
    int mid=(l+r)>>1;
    if(L<=mid) add(x<<1,l,mid,L,R,c);
    if(mid<R) add(x<<1|1,mid+1,r,L,R,c);
    pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
    {
        return s(x);
    }
    pushdown(x,l,r);
    int mid=(l+r)>>1;
    int res=0;
    if(L<=mid) res=mod(res+query(x<<1,l,mid,L,R));
    if(mid<R) res=mod(res+query(x<<1|1,mid+1,r,L,R));
    return res;
}
//-------------------以上线段树 ----------------------------------- 
void work1(int x,int y,int z)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        add(1,1,tot,dfn[top[y]],dfn[y],z);
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    add(1,1,tot,dfn[x],dfn[y],z);
}
int work2(int x,int y)
{
    int sum=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        sum=(sum+query(1,1,tot,dfn[top[y]],dfn[y]))%p;
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    sum=(sum+query(1,1,tot,dfn[x],dfn[y]))%p;
    return sum;
}
void work3(int x,int z)
{
    add(1,1,tot,dfn[x],dfn2[x],z);
}
int work4(int x)
{
    int sum=0;
    sum=(sum+query(1,1,tot,dfn[x],dfn2[x]))%p;
    return sum;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&root,&p);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    int x,y;
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs1(root,-1,1);
    dfs2(root,-1);
    fa[root]=-1;

    build(1,1,tot);
    int op,z;
    while(m)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            work1(x,y,z);
        }
        else if(op==2)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",work2(x,y));
        }
        else if(op==3)
        {
            scanf("%d%d",&x,&z);
            work3(x,z);
        }
        else{
            scanf("%d",&x);
            printf("%d\n",work4(x));
        }
        m--;
    }
    return 0;
}

 

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

树链剖分小结

树链剖分详解

树链剖分

树链剖分 入门

树链剖分

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