树链剖分笔记(一)-------初识树链剖分

写在前面

首先,在学树链剖分之前最好先把 LCA、树形DP、DFS序 这三个知识点学了
emm还有必备的 链式前向星、线段树 也要先学了。

如果这三个知识点没掌握好的话,树链剖分难以理解也是当然的。


树链剖分

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

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

目录:

概念

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

dfs1()

这个dfs要处理几件事情:

  • 标记每个点的深度dep[]
  • 标记每个点的父亲fa[]
  • 标记每个非叶子节点的子树大小(含它自己)
  • 标记每个非叶子节点的重儿子编号son[]

  • inline void dfs1(int x,int f,int deep){//x当前节点,f父亲,deep深度 
    dep[x]=deep;//标记每个点的深度 
    fa[x]=f;//标记每个点的父亲 
    siz[x]=1;//标记每个非叶子节点的子树大小 
    int maxson=-1;//记录重儿子的儿子数 
    for(Rint i=beg[x];i;i=nex[i]){
        int y=to[i];
        if(y==f)continue;//若为父亲则continue 
        dfs1(y,x,deep+1);//dfs其儿子 
        siz[x]+=siz[y];//把它的儿子数加到它身上 
        if(siz[y]>maxson)son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号 
    }
    }//变量解释见最下面

dfs2()

这个dfs2也要预处理几件事情

  • 标记每个点的新编号
  • 赋值每个点的初始值到新编号上
  • 处理每个点所在链的顶端
  • 处理每条链

顺序:先处理重儿子再处理轻儿子,理由后面说

inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点 
    id[x]=++cnt;//标记每个点的新编号 
    wt[cnt]=w[x];//把每个点的初始值赋到新编号上来 
    top[x]=topf;//这个点所在链的顶端 
    if(!son[x])return;//如果没有儿子则返回 
    dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理 
    for(Rint i=beg[x];i;i=nex[i]){
        int y=to[i];
        if(y==fa[x]||y==son[x])continue;
        dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链 
    }
}//变量解释见最下面

处理问题

Attention 重要的来了!!!
前面说到dfs2的顺序是先处理重儿子再处理轻儿子
我们来模拟一下:

  • 因为顺序是先重再轻,所以每一条重链的新编号是连续的
  • 因为是dfs,所以每一个子树的新编号也是连续的

现在回顾一下我们要处理的问题

  • 处理任意两点间路径上的点权和
  • 处理一点及其子树的点权和
  • 修改任意两点间路径上的点权
  • 修改一点及其子树的点权

1、当我们要处理任意两点间路径时:
设所在链顶端的深度更深的那个点为x点

  • ans加上x点到x所在链顶端 这一段区间的点权和
  • 把x跳到x所在链顶端的那个点的上面一个点

不停执行这两个步骤,直到两个点处于一条链上,这时再加上此时两个点的区间和即可

这时我们注意到,我们所要处理的所有区间均为连续编号(新编号),于是想到线段树,用线段树处理连续编号区间和

每次查询时间复杂度为O(log2n)O(log2⁡n)

inline int qRange(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){//当两个点不在同一条链上 
        if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点
        res=0;
        query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
        ans+=res;
        ans%=mod;//按题意取模 
        x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
    }
    //直到两个点处于一条链上
    if(dep[x]>dep[y])swap(x,y);//把x点深度更深的那个点
    res=0;
    query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
    ans+=res;
    return ans%mod;
}//变量解释见最下面

2、处理一点及其子树的点权和:
想到记录了每个非叶子节点的子树大小(含它自己),并且每个子树的新编号都是连续的
于是直接线段树区间查询即可

时间复杂度为O(logn)O(log⁡n)

inline int qSon(int x){
    res=0;
    query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1 
    return res;
}

当然,区间修改就和区间查询一样的啦~~

inline void updRange(int x,int y,int k){
    k%=mod;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,id[x],id[y],k);
}

inline void updSon(int x,int k){
    update(1,1,n,id[x],id[x]+siz[x]-1,k);
}//变量解释见最下面

建树

既然前面说到要用线段树,那么按题意建树就可以啦!
不过,建树这一步当然是在处理问题之前哦~

AC代码:

inline void updRange(int x,int y,int k){
    k%=mod;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,id[x],id[y],k);
}

inline void updSon(int x,int k){
    update(1,1,n,id[x],id[x]+siz[x]-1,k);
}//变量解释见最下面

然后换一种码风:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0'; 
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,r,q,cnt,rec;
int son[MAXN],rank[MAXN],tid[MAXN],nxt[MAXN<<1],head[MAXN],top[MAXN],fa[MAXN];
int to[MAXN<<1],val[MAXN],sum[MAXN<<2],add[MAXN<<2],dep[MAXN],siz[MAXN];
void addedge(int x,int y){
    to[rec]=y;nxt[rec]=head[x];head[x]=rec;rec++;
    to[rec]=x;nxt[rec]=head[y];head[y]=rec;rec++; 
}
void up(int node){sum[node]=(sum[node<<1]+sum[node<<1|1])%q;}
void down(int node,int ls,int rs){   //下推标记
    if(add[node]){
        add[node<<1]+=add[node];
        add[node<<1|1]+=add[node];
        sum[node<<1]+=add[node]*ls;
        sum[node<<1|1]+=add[node]*rs;
        sum[node<<1]%=q;sum[node<<1|1]%=q;
        add[node]=0;
    } 
}
void dfsI(int x,int father){   //第一个DFS
    fa[x]=father;son[x]=-1;
    dep[x]=dep[fa[x]]+1;siz[x]=1;
    for(int i=head[x];i!=-1;i=nxt[i]){
        int go=to[i];
        if(go==fa[x]) continue;
        dfsI(go,x);siz[x]+=siz[go];
        if(son[x]==-1||siz[go]>siz[son[x]]) son[x]=go;
    } 
}
void dfsII(int x,int t){    //第二个DFS
    tid[x]=++cnt;
    rank[cnt]=val[x]%q;
    top[x]=t;
    if(son[x]==-1) return;
    dfsII(son[x],t);
    for(int i=head[x];i!=-1;i=nxt[i]){
        int go=to[i];
        if(go==fa[x]||go==son[x]) continue;
        dfsII(go,go);
    }
}
void build(int node,int l,int r){   //建树
    if(l==r){
        sum[node]=rank[l];
        return;
    } 
    int mid=(l+r)>>1; 
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    up(node);
}
void update(int node,int l,int r,int L,int R,int ad){   //线段树区间修改
    if(L<=l&&r<=R){
        sum[node]+=ad*(r-l+1);sum[node]%=q;
        add[node]+=ad;return;
    }
    int mid=(l+r)>>1;
    down(node,mid-l+1,r-mid);
    if(L<=mid) update(node<<1,l,mid,L,R,ad);
    if(R>mid) update(node<<1|1,mid+1,r,L,R,ad); 
    up(node);
}
int query(int node,int l,int r,int L,int R){   //区间求和
    if(L<=l&&r<=R){
        return sum[node];
    } 
    int mid=(l+r)>>1,ans=0;
    down(node,mid-l+1,r-mid);
    if(L<=mid) ans+=query(node<<1,l,mid,L,R);
    if(R>mid) ans+=query(node<<1|1,mid+1,r,L,R);
    ans%=q;
    return ans; 
}
int qTree(int x){    //这是操作4
    return query(1,1,n,tid[x],tid[x]+siz[x]-1);
} 
void upTree(int x,int ad){     //操作3
    update(1,1,n,tid[x],tid[x]+siz[x]-1,ad);
}
int queryLink(int x,int y){    //DFS求链上权值和
    int ans=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        ans+=query(1,1,n,tid[top[y]],tid[y]);
        ans%=q;
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    ans+=query(1,1,n,tid[x],tid[y]);
    ans%=q;
    return ans;
}
void updateLink(int x,int y,int ad){    //链上修改
    while(top[x]!=top[y]){
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        update(1,1,n,tid[top[y]],tid[y],ad);
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    update(1,1,n,tid[x],tid[y],ad); 
} 
int main()
{
    memset(head,-1,sizeof(head));
    n=read();m=read();r=read();q=read();
    for(int i=1;i<=n;i++) val[i]=read();
    for(int i=1;i<=n-1;i++){
        int x=read(),y=read();
        addedge(x,y);
    }
    dfsI(r,0);
    dfsII(r,r);
    build(1,1,n);
    for(int i=1;i<=m;i++){
        int x=read();
        if(x==1){
            int u=read(),v=read(),ad=read();
            updateLink(u,v,ad);
        }
        if(x==2){
            int u=read(),v=read();
            printf("%d\n",queryLink(u,v));
        } 
        if(x==3){
            int u=read(),ad=read();
            upTree(u,ad);
        }
        if(x==4){
            int u=read();
            printf("%d\n",qTree(u));
        } 
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/fatalzyc/article/details/80342443
今日推荐