树链剖分

详细

树链剖分就是两次dfs处理出这样一个东西:

  • f a [ i ] : i 的 父 亲 节 点 fa[i]:i的父亲节点 fa[i]i
  • d [ i ] : i 的 深 度 d[i]:i的深度 d[i]:i
  • s z [ i ] : i 的 儿 子 节 点 数 量 sz[i]:i的儿子节点数量 sz[i]:i
  • s o n [ i ] : i 的 重 儿 子 son[i]:i的重儿子 son[i]:i
  • i d [ i ] : i 的 d f s 序 编 号 ( 优 先 遍 历 重 儿 子 的 先 序 遍 历 ) id[i]:i的dfs序编号(优先遍历重儿子的先序遍历) id[i]:idfs()
  • r k [ i ] : 第 i 个 遍 历 到 的 节 点 ( i d 的 映 射 ) rk[i]:第i个遍历到的节点(id的映射) rk[i]:i(id)
  • t o p [ i ] : i 的 所 在 链 的 链 头 top[i]:i的所在链的链头 top[i]:i
  • b o t [ i ] : i 的 所 在 链 的 链 尾 bot[i]:i的所在链的链尾 bot[i]:i

以下信息来自洛谷 https://www.luogu.org/problemnew/solution/P3384

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

在这里插入图片描述
在这里插入图片描述

板子

int n;vector<int>g[MX];
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
/*   父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾     */
int clockCnt;
void dfs(int now,int dd){
    d[now]=dd;sz[now]=1;
    int maxson=-1;
    for(auto i:g[now]){
        if(i.v==fa[now])continue;
        fa[i.v]=now;
        dis[i.v]=dis[now]+i.w;
        dfs(i.v,dd+1);
        sz[now]+=sz[i.v];
        if(sz[i.v]>maxson) son[now]=i.v,maxson=sz[i.v]; //重儿子
    }
}
void redfs(int now,int tp){
    top[now]=tp;//保存当前节点所在链的顶端节点
    id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
    if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
    else bot[now]=now;
    for(auto i:g[now]){
        if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻链
    }
}
int LCA(int u,int v){
     while(top[u]!=top[v]){//走到同一重链
         if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
             swap(u,v);
         u=fa[top[u]]; //跳到链顶的上面
     }
     if(d[u]<d[v]) //在同一个条重链
         return u;
    return v;
}

题目

洛谷 p3379 //lca模板

树链剖分求lca时,内存不用开 n l o g nlog nlog,单个询问也是 l o g log log

/*   Author : Rshs
 *   Data : 2019-09-26-14.37
 */
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MX = 1e6+5;
/****************************************************************************/
struct eno{int to,ne;};
eno e[MX*2];int head[MX],edgenum;
void addEdge(int u,int v){
    e[edgenum].to=v;//e[i].to表示第i条边的终点
    e[edgenum].ne=head[u];//head[i]表示以i为起点的最后一条边的储存位置
    head[u]=edgenum++;
}
void initvector(){
    edgenum=0;
    memset(head,-1,sizeof(head));
}
/****************************************************************************/
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
/*   父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾     */
int clockCnt;
void dfs(int now,int dd){
    d[now]=dd;sz[now]=1;
    for(int ii=head[now];ii!=-1;ii=e[ii].ne){
        int i=e[ii].to;
        if(i==fa[now])continue;
        fa[i]=now;
        dfs(i,dd+1);
        sz[now]+=sz[i];
        if(sz[i]>sz[son[now]]) son[now]=i; //重儿子
    }
}
void redfs(int now,int tp){
    top[now]=tp;//保存当前节点所在链的顶端节点
    id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
    if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
    else bot[now]=now;
    for(int ii=head[now];ii!=-1;ii=e[ii].ne){
        int i=e[ii].to;
        if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻儿子
    }
}
/****************************************************************************/
int LCA(int u,int v){
     while(top[u]!=top[v]){//走到同一重链
         if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
             swap(u,v);
         u=fa[top[u]]; //跳到链顶的上面
     }
     if(d[u]<d[v]) //在同一个条重链
         return u;
    return v;
}
int main(){
    initvector();
    int n,m,s;cin>>n>>m>>s;
    for(int i=1;i<n;i++){
        int sa,sb;scanf("%d %d",&sa,&sb);
        addEdge(sa,sb);addEdge(sb,sa);
    }
    dfs(s,1);
    redfs(s,s);
    while(m--){
        int sa,sb;scanf("%d %d",&sa,&sb);
        cout<<LCA(sa,sb)<<'\n';
    }
    return 0;
}

洛谷 P3384 //树链剖分+线段树维护点上信息

树链剖分后,一条重链上的 i d id id是连续的,子树节点的 i d id id也连续

线段树维护的序列是 r k [ a [ i ] ] rk[a[i]] rk[a[i]]
更新两点间的路径上的点:在求 l c a lca lca的过程中,利用 t o p , i d top,id topid信息,边向上跳,边更新链,复杂度 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn)

/*   Author : Rshs
 *   Data : 2019-09-26-19.10
 */
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MX = 1e6+5;
int n,m,rt;LL a[MX],mo;
/****************************************************************************/
struct eno{int to,ne;};
eno e[MX*2];int head[MX],edgenum;
void addEdge(int u,int v){
    e[edgenum].to=v;//e[i].to表示第i条边的终点
    e[edgenum].ne=head[u];//head[i]表示以i为起点的最后一条边的储存位置
    head[u]=edgenum++;
}
void initvector(){
    edgenum=0;
    memset(head,-1,sizeof(head));
}
/****************************************************************************/
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
/*   父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾     */
int clockCnt;
void dfs(int now,int dd){
    d[now]=dd;sz[now]=1;
    for(int ii=head[now];ii!=-1;ii=e[ii].ne){
        int i=e[ii].to;
        if(i==fa[now])continue;
        fa[i]=now;
        dfs(i,dd+1);
        sz[now]+=sz[i];
        if(sz[i]>sz[son[now]]) son[now]=i; //重儿子
    }
}
void redfs(int now,int tp){
    top[now]=tp;//保存当前节点所在链的顶端节点
    id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
    if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
    else bot[now]=now;
    for(int ii=head[now];ii!=-1;ii=e[ii].ne){
        int i=e[ii].to;
        if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻儿子
    }
}
/****************************************************************************/
LL lz[MX],T[MX];
inline void pushUp(int rt){
    T[rt]=(T[rt<<1]+T[rt<<1|1])%mo;
}
void pushDown(int rt,int le){
    if(lz[rt]){
        lz[rt<<1]+=lz[rt];
        lz[rt<<1|1]+=lz[rt];
        T[rt<<1]+=(lz[rt]*(le-(le>>1))%mo);
        T[rt<<1|1]+=(lz[rt]*(le>>1)%mo);
        T[rt<<1]%=mo;
        T[rt<<1|1]%=mo;
        lz[rt]=0;
    }
}
void build(int l,int r,int rt){
    lz[rt]=0;
    if(l==r){
        T[rt]=a[rk[l]]%mo;
        return;
    }
    int m=(l+r)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    pushUp(rt);
}
void update(int L,int R,LL c,int l,int r,int rt){
    if(l>=L&&r<=R){
        lz[rt]+=c;
        T[rt]+=(c*(r-l+1)%mo);T[rt]%=mo;
        return;
    }
    pushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(m>=L) update(L,R,c,l,m,rt<<1);
    if(m<R)  update(L,R,c,m+1,r,rt<<1|1);
    pushUp(rt);
}
LL query(int L,int R,int l,int r,int rt){
    if(l>=L&&r<=R)
        return T[rt];
    pushDown(rt,r-l+1);
    int m=(l+r)>>1;
    LL re=0;
    if(m>=L) re+=query(L,R,l,m,rt<<1);
    if(m<R)  re+=query(L,R,m+1,r,rt<<1|1);
    return re%mo;
}

int main(){
    initvector();
    cin>>n>>m>>rt>>mo;
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<n;i++){
        int sa,sb;scanf("%d %d",&sa,&sb);
        addEdge(sa,sb);addEdge(sb,sa);
    }
    dfs(rt,1);
    redfs(rt,rt);
    build(1,n,1);
    while(m--){
        int op;scanf("%d",&op);
        if(op==1){
            int u,v,z;scanf("%d%d%d",&u,&v,&z);
            z=z%mo;
            while(top[u]!=top[v]){//走到同一重链
                if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
                    swap(u,v);
                int id1=id[top[u]],id2=id[u];
                update(id1,id2,z,1,n,1);
                u=fa[top[u]]; //跳到链顶的上面
            }
            if(d[u]>d[v]){
                swap(u,v);
            }
            update(id[u],id[v],z,1,n,1);
        }
        if(op==2){
            int u,v;scanf("%d%d",&u,&v);
            LL re=0;
            while(top[u]!=top[v]){//走到同一重链
                if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
                    swap(u,v);
                int id1=id[top[u]],id2=id[u];
                re=(re+query(id1,id2,1,n,1))%mo;
                u=fa[top[u]]; //跳到链顶的上面
            }
            if(d[u]>d[v]){
                swap(u,v);
            }
            re=re+query(id[u],id[v],1,n,1);
            cout<<re%mo<<'\n';
        }
        if(op==3){
            int x,z;scanf("%d%d",&x,&z);
            update(id[x],id[x]+sz[x]-1,z,1,n,1);
        }
        if(op==4){
            int x;scanf("%d",&x);
            cout<<query(id[x],id[x]+sz[x]-1,1,n,1)%mo<<'\n';
        }
    }
    return 0;
}

POJ 2763 // 树链剖分+线段树维护边上信息

以前写过树状数组+lca+离线的做法,现在写一下树剖
线段树维护一条重链 i d id id相邻的节点的距离。对于轻边:记录到父节点的距离。
其实我的写麻烦了,直接线段树维护当前节点到父亲节点的距离就好,差不多,将就看。

#include  <map>
#include  <set>
#include  <cmath>
#include  <queue>
#include  <cstdio>
#include  <vector>
#include  <climits>
#include  <cstring>
#include  <cstdlib>
#include  <iostream>
#include  <algorithm>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MX = 1e6+5;

/****************************************************************************/
struct eno{int to,ne,co;};
eno e[MX*2];int head[MX],edgenum;
void addEdge(int u,int v,int co){
    e[edgenum].to=v;//e[i].to表示第i条边的终点
    e[edgenum].co=co;
    e[edgenum].ne=head[u];//head[i]表示以i为起点的最后一条边的储存位置
    head[u]=edgenum++;
}
void initvector(){
    edgenum=0;
    memset(head,-1,sizeof(head));
}
/****************************************************************************/
int fa[MX],d[MX],sz[MX],son[MX],id[MX],rk[MX],top[MX],bot[MX];
LL dis[MX];
/*   父亲,深度,子节点数,重儿子, dfs序,dfs映射,链头, 链尾     */
int clockCnt;
void dfs(int now,int dd){
    d[now]=dd;sz[now]=1;
    for(int ii=head[now];ii!=-1;ii=e[ii].ne){
        int i=e[ii].to;
        if(i==fa[now])continue;
        dis[i]=e[ii].co; //记录到父亲
        fa[i]=now;
        dfs(i,dd+1);
        sz[now]+=sz[i];
        if(sz[i]>sz[son[now]]) son[now]=i; //重儿子
    }
}
void redfs(int now,int tp){
    top[now]=tp;//保存当前节点所在链的顶端节点
    id[now]=++clockCnt;rk[clockCnt]=now;//标记dfs序并反向映射
    if(son[now])redfs(son[now],tp),bot[now]=bot[son[now]];//选择择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是tt
    else bot[now]=now;
    for(int ii=head[now];ii!=-1;ii=e[ii].ne){
        int i=e[ii].to;
        if(i!=fa[now]&&i!=son[now])redfs(i,i);//进入轻儿子
    }
}
/****************************************************************************/
LL T[MX];
inline void pushUp(int rt){
    T[rt]=T[rt<<1]+T[rt<<1|1];
}

void build(int l,int r,int rt){
    if(l==r){
        T[rt]=dis[rk[l+1]];  //第i序的节点到父节点的距离
        return;
    }
    int m=(l+r)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    pushUp(rt);
}
void update(int L,int R,LL c,int l,int r,int rt){
    if(l>=L&&r<=R){
        T[rt]=c;
        return;
    }
    int m=(l+r)>>1;
    if(m>=L) update(L,R,c,l,m,rt<<1);
    if(m<R)  update(L,R,c,m+1,r,rt<<1|1);
    pushUp(rt);
}
LL query(int L,int R,int l,int r,int rt){
    if(l>=L&&r<=R)
        return T[rt];
    int m=(l+r)>>1;
    LL re=0;
    if(m>=L) re+=query(L,R,l,m,rt<<1);
    if(m<R)  re+=query(L,R,m+1,r,rt<<1|1);
    return re;
}
int sa[MX],sb[MX],sc[MX];int n,q,s;
LL LCA(int u,int v){
    LL re=0;
    while(top[u]!=top[v]){//走到同一重链
        if(d[top[u]]<d[top[v]])//把优先处理deep[top[]]大的,跳到较深度较大的地方
         swap(u,v);
        if(id[top[u]]<=id[u]-1)
        re=re+query(id[top[u]],id[u]-1,1,n-1,1); //到链头
        re=re+dis[top[u]];  //到父亲
        u=fa[top[u]]; //跳到链顶的上面
    }
    if(d[u]>d[v]) //在同一个条重链
        swap(u,v);
    if(id[u]<=id[v]-1)
        re=re+query(id[u],id[v]-1,1,n-1,1);  //到u
    return re;
}

int main(){
    initvector();
    cin>>n>>q>>s;
    for(int i=1;i<n;i++){
        scanf("%d %d %d",&sa[i],&sb[i],&sc[i]);
        addEdge(sa[i],sb[i],sc[i]),addEdge(sb[i],sa[i],sc[i]);
    }
    dfs(1,1);
    redfs(1,1);
    build(1,n-1,1);
    for(int i=1;i<=q;i++){
        int op;scanf("%d",&op);
        if(op==0){
            int x;scanf("%d",&x);
            cout<<LCA(s,x)<<'\n';
            s=x;
        }
        else {
            int x,y;scanf("%d%d",&x,&y);
            int l=id[sa[x]],r=id[sb[x]];
            if(l>r) swap(l,r);
            if(l+1==r){
                update(l,l,y,1,n-1,1);
            }
            if(fa[sa[x]]==sb[x])dis[sa[x]]=y;  //跟新到父节点
            else dis[sb[x]]=y;
        }
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值