轻重链剖分

树链剖分,一个让你代码量翻倍的能优雅解决树上问题的神奇方式。

树剖就是把树拆成链,拆成序列,然后就可以用序列方式处理这棵树。
一道树剖题Luogu 3384

题目大概意思:给一有根树,给定四种操作

  1. 树上两点,及他俩路径上点权+\(d\)
  2. 查询树上两点及路径上点权和
  3. 某点及子树+\(d\)
  4. 查询某点及子树求和
    对于2 4操作,查询的结果对给出的数取模并输出。
    于是就开始了愉快的树链剖分,两次dfs将树拆成链,怎么拆?我们定义一个点,它的重儿子是子树\(size\)最大的一个儿子,然后一直沿着重儿子走就是一条重链。除了重儿子剩下的都是轻儿子。
void Dfs(int x,int fa){
    f[x]=fa;dep[x]=dep[fa]+1;si[x]=1;//初始化父亲,深度,子树大小
    int ms=-1;//儿子中子树最大值初始化
    for(int p=h[x];p;p=e[p].nxt){
        int to=e[p].to;//遍历所有点 前向星存边
        if(to==fa)continue;//是父亲就别访问了,从父亲来的,不必反复横跳
        Dfs(to,x);//处理儿子
        si[x]+=si[to];//自己的子树大小=初始的自己+第一个儿子子树+第二个儿子....  所以一直加就好了
        if(si[to]>ms){//如果儿子大于目前最大的儿子大小
            ms=si[to];//新的最大值
            wson[x]=to;//新的重儿子
        }
    }
}

第一遍dfs得到一些信息,探探路,把重儿子是谁什么的摸透了,再来拆重儿子链。

dfs(rt,rt);//两个要素 第一个是当前点 第二个是目前链的顶点,所以一开始要传根节点和根节点
void dfs(int x,int topf){
    top[x]=topf;//你被访问到,如果你是轻儿子,那么topf进来的时候会改成你,链顶是你,否则你是重儿子,你在重链上,topf就是父亲传下来的顶端
    id[x]=++tot;//时间戳,这是第几个结点
    w[tot]=a[x];//变成新的序列之后,新的序列第tot个点的权值是x的权值
    if(!wson[x])return;//没有重儿子->没有儿子->叶子节点->返回,如果不返回可能会叶子->0->0->0...套娃
    dfs(wson[x],topf);//访问重儿子,重儿子在重链上,传topf
    for(int p=h[x];p;p=e[p].nxt){
        int to=e[p].to;//遍历
        if(to==f[x])continue;//父亲不去
        if(to==wson[x])continue;//遍历过不去
        dfs(to,to);//这里要改成to,因为除了重儿子外都是轻儿子,如果是轻儿子,topf就要改成它自己
    }
}

轻儿子一定是一个重链的链头,因为它的兄弟是重儿子,就说明父亲的重链不在自己这里,所以自己要重新开一个重链,走自己的重儿子让别人说去吧,所以轻儿子遍历传to to,相当是新的链
于是就剖完了,对于原来树上点 \(id[x]\)是它第几个被访问到,就是他在 树 转化成的 序列 里的下标[位置 \(position\)],\(w\)数组就是序列中点的值,是将树变为序列之后,每一个点的值。\(w[id[x]]==a[x]\)。以后树上的处理就在序列里处理就行了,已经用序列存下了树所有点的值,也知道树上每一个点在序列里的位置。
注意一个非常神奇的地方,如果你要修改查询一个子树,这个子树一定是在序列里连续的,为什么?因为dfs是深度优先,所以一定会先访问完这个点所有的值,所以如果要修改一个点\(x\),因为是连续的,其实就等同于修改查询区间\(w[id_x,id_x+size_x-1]\)\(x\)在序列中的位置加上子树大小-1就是\(x\)最后一个结点的位置,所以对这个区间操作,就是对x子树操作。

void subtreemodify(int x,int z){
    modify(1,1,n,id[x],id[x]+si[x]-1,z);//子树加
    return;
}
LL subtreequery(int x){
    return query(1,1,n,id[x],id[x]+si[x]-1);//子树和
}

然后区间加和区间和就用线段树就可以了,注意线段树不要去维护一开始输入的\(a\)数组,而要维护转化为序列的\(w\)数组,这个序列中x和其子树才是连续的一段,可以用\(id\)来搞。
总结

  • 对于子树修改
    1. 因为x及子树是序列中连续的区间,所以找到x的位置,线段树将区间直接修改
  • 对于子树查询
    1. 因为连续,线段树直接查询

然后就是令人绝望的两点间修改查询。思想就是一条链一条链改,比如两个点,我先看看是不是一个链,不是一个链,深度比较深的点优先往上跳。
先对这个点直到链首查询或修改,为什么可以这么做?因为我们优先走重儿子,所以链首到链上一个点必然也是序列中连续的区间,所以依然可以线段树\(query\)\(modify\),然后这个点再跳到链首的父亲[跳到链首可能就一直自己跳自己死循环]。
直到一个链之后,我们用线段树直接查询两点间,或者修改两点间[用\(id\)来修改查询]。
总结 修改和查询统称为操作[先总结再上代码

  1. 查询是否一个链
    • 如果不是一个链
      1. 比较链头深度
      2. 链头深的点,因为链上点在区间连续,所以直接操作这个点到其链头
      3. 链头深的点跳到链头的父亲
      4. 返回查询是否一个链操作
    • 如果是一个链
      1. 修改两点间[\(x.id\)~\(y.id\)]
      2. 所有操作均已结束,结束操作。
      是不是忘了什么
void chainmodify(int x,int y,int z){
    z%=md;//取模
    while(top[x]!=top[y]){//不是一个链[chain]
        if(dep[top[x]]<dep[top[y]])swap(x,y);//取深度深的[大的
        modify(1,1,n,id[top[x]],id[x],z);//操作
        x=f[top[x]];//跳
    }
    if(dep[x]>dep[y])swap(x,y);//同一个链上了 取深度小的[看个人喜好?]当x,因为深度小id小,习惯写的时候先x后y
    modify(1,1,n,id[x],id[y],z);//操作
    return;//结束
}
LL chainquery(int x,int y){//同理
    LL re=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        re+=query(1,1,n,id[top[x]],id[x]);
        x=f[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    re+=query(1,1,n,id[x],id[y]);
    return re;
}

完整代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+8;
typedef long long LL;
LL md;
LL a[N],w[N];
int top[N],tot,id[N];
int n,m,rt;
int h[N],cnt=1;
struct edg{
    int to,nxt;
}e[N<<1];
int f[N],dep[N],wson[N],si[N];
struct Node{
    LL sum,lz;
}tre[N<<2];
void down(int x,int l,int r){
    //puts("-------");
    //system("pause");
    int mid=(l+r)/2,ls=x*2,rs=x*2+1;
    tre[ls].sum+=(mid-l+1)*tre[x].lz;
    tre[ls].sum%=md;
    tre[rs].sum+=(r-mid)*tre[x].lz;
    tre[rs].sum%=md;
    tre[ls].lz=tre[ls].lz+tre[x].lz;
    tre[rs].lz=tre[rs].lz+tre[x].lz;
    tre[x].lz=0;
    return;
}
void modify(int x,int l,int r,int L,int R,int d){
    int mid=(l+r)/2,ls=x*2,rs=x*2+1;
    if(l>=L && r<=R){
        tre[x].sum+=(r-l+1)*d;
        tre[x].sum%=md;
        tre[x].lz+=d;
        return;
    }
    down(x,l,r);
    if(mid>=L){
        modify(ls,l,mid,L,R,d);
    }
    if(mid+1<=R){
        modify(rs,mid+1,r,L,R,d);
    }
    tre[x].sum=(tre[ls].sum+tre[rs].sum)%md;
    return;
}
LL query(int x,int l,int r,int L,int R){
    int mid=(l+r)/2,ls=x*2,rs=x*2+1;
    if(L<=l && r<=R){
        return tre[x].sum;
    }
    down(x,l,r);
    LL ans=0;
    if(L<=mid){
        ans+=query(ls,l,mid,L,R);
        ans%=md;
    }
    if(mid+1<=R){
        ans+=query(rs,mid+1,r,L,R);
        ans%=md;
    }
    return ans;
}
void Build(int x,int l,int r){
    int mid=(l+r)/2,ls=x*2,rs=x*2+1;
    if(l==r){
        tre[x].sum=w[l]%md;//l其实是id 就是tot
    }
    else{
        Build(ls,l,mid);
        Build(rs,mid+1,r);
        tre[x].sum=(tre[ls].sum+tre[rs].sum)%md;
    }
}
void chainmodify(int x,int y,int z){
    z%=md;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);//深度小的高
        modify(1,1,n,id[top[x]],id[x],z);
        x=f[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);//同一个链上了 深度大的id大
    modify(1,1,n,id[x],id[y],z);
    return;
}
LL chainquery(int x,int y){
    LL re=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        re+=query(1,1,n,id[top[x]],id[x]);
        x=f[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    re+=query(1,1,n,id[x],id[y]);
    return re;
}
void subtreemodify(int x,int z){
    modify(1,1,n,id[x],id[x]+si[x]-1,z);
    return;
}
LL subtreequery(int x){
    return query(1,1,n,id[x],id[x]+si[x]-1);
}
void Dfs(int x,int fa){
    f[x]=fa;dep[x]=dep[fa]+1;si[x]=1;
    int ms=-1;
    for(int p=h[x];p;p=e[p].nxt){
        int to=e[p].to;
        if(to==fa)continue;
        Dfs(to,x);
        si[x]+=si[to];
        if(si[to]>ms){//如果儿子大于目前最大的儿子大小
            ms=si[to];//新最大
            wson[x]=to;//新重儿子
        }
    }
}
void dfs(int x,int topf){
    top[x]=topf;//链顶端结点
    id[x]=++tot;//时间戳
    w[tot]=a[x];//tot个点的权值是x的权值
    if(!wson[x])return;//没有重儿子->没有儿子->叶子节点->返回
    dfs(wson[x],topf);
    for(int p=h[x];p;p=e[p].nxt){
        int to=e[p].to;
        if(to==f[x])continue;
        if(to==wson[x])continue;
        dfs(to,to);
    }
}
void add(int u,int v){
    e[++cnt]=(edg){v,h[u]};
    h[u]=cnt;
}
int main(){
    scanf("%d%d%d%lld",&n,&m,&rt,&md);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int u,v,i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    Dfs(rt,0);
    dfs(rt,rt);
    Build(1,1,n);
    for(int i=1;i<=m;i++){
        int opt,x,y,z;
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d%d%d",&x,&y,&z);
            chainmodify(x,y,z);
        }
        if(opt==2){
            scanf("%d%d",&x,&y);
            LL ans=chainquery(x,y);
            ans%=md;
            printf("%lld\n",ans);
        }
        if(opt==3){
            scanf("%d%d",&x,&z);
            subtreemodify(x,z);
        }
        if(opt==4){
            scanf("%d",&x);
            LL ans=subtreequery(x);
            ans%=md;
            printf("%lld\n",ans);
        }
    }
    return 0;
}
posted @ 2020-08-25 16:51  Z_char  阅读(268)  评论(0编辑  收藏  举报