【笔记+模板】树链剖分

//以下定义及说明,部分来自不知名神犇的资料,侵删

定义

1.定义:

树链剖分*,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。—-摘自百度百科

树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。

每一个节点都属于且仅属于一条重链;

一般用线段树来维护,维护的是点权;
如果要维护边权,可以将边权赋给to点,根节点赋极小值。
注意求v–u路径长度时,v节点的值是fa[v]–v的边权,不应包含在内;

2.解释

变量解释:
sz[i] : 以i为根节点的子树的节点数,包括i节点本身。
son[i] : i节点的重儿子
inseg[i] : 原树内i号节点在线段树内的编号
intr[i] : 线段树内i号节点在原树内的编号
top[i] : i节点所在重链的头节点
cnt_seg : 线段树节点编号计数器
名词解释:
重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边
3.性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];(反证,若大于,则必为重边);
性质2:从根到某一点的路径上轻边、重链的个数都不大于logn。

算法流程

重链剖分:

dfs1() 找重边,求出fa,deep,son,sz

void dfs1(int x,int fx){
    fa[x]=fx;deep[x]=deep[fx]+1;
    sz[x]=1;
    for(int i=first[i];i;i=next[i]){
        int v=e[i].t;
        if(v==fx) continue;
        dfs1(v,x);
        sz[x]+=sz[v];
        if(!son[x]||sz[son[x]]<sz[v])//记录重儿子
        son[x]=v;
    }
    return ;
}

dfs2() 将重边连成重链
以根节点为起点,往下拉重链,不在当前重链上的节点,以其为根节点往下拉重链

1.对于重儿子,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
2.对于轻儿子,以其本身重新拉重链。

void dfs2(int u,int tp){
    top[u]=tp;
    inseg[u]=++cnt_seg;
    intr[cnt_seg]=u;
    if(!son[u]) return ;//若为叶子节点,直接return
    dfs2(son[u],tp);//重儿子
    for(int i=first[u];i;i=next[i]){
        int v=e[i].t;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);//轻儿子
    }
    return ;
}

维护重链:

一条重链就是一段连续区间,将所有重链首尾相接,用线段树维护。

修改/查询:

修改查询操作类似,以修改为例。
单点修改
直接用线段树修改单点。
区间修改 (u,v)
1.u,v在一条重链上,直接用线段树修改区间(inseg[u],inseg[v])。
2.不在一条重链上,边修改边向一条重链上靠。
选择deep[top[]]大的点u,//防止跳过界
线段树修改区间(inseg[top[u]],inseg[u]);
将u跳至fa[top[u]],直至u,v在一条重链上,重复1。
注:
跳至同一条重链时,深度小的点即为lca,这就是树剖求lca,详见小机房的树。

蒋一瑶神犇的课件
https://wenku.baidu.com/view/a088de01eff9aef8941e06c3.html

模板:

题目:树的统计

http://codevs.cn/problem/2460/

#include<iostream>
#include<cstdio>
using namespace std;
const int N=50000+60,M=75000+60;
int n,m,a,b,c,tot,cnt_seg;

struct seg_tree{
    int l,r,sum,maxx;
}seg[N<<2];
int e[M],first[N],next[M],fa[N],deep[N],top[N],inseg[N],intr[N],sz[N],son[N],val[N];


void build(int ff,int tt){
    e[++tot]=tt;
    next[tot]=first[ff];
    first[ff]=tot;
    return ;
}
void dfs1(int x,int fx){
    fa[x]=fx;deep[x]=deep[fx]+1;
    sz[x]=1;
    for(int i=first[x];i;i=next[i]){
        if(e[i]==fx) continue;
        dfs1(e[i],x);
        sz[x]+=sz[e[i]];
        if(!son[x]||sz[son[x]]<sz[e[i]]) 
        son[x]=e[i];

    }
    return ;
}
void dfs2(int t,int tp){
    top[t]=tp;
    inseg[t]=++cnt_seg;
    intr[cnt_seg]=t;
    if(!son[t]) return ;
    dfs2(son[t],tp);
    for(int i=first[t];i;i=next[i]){
        int v=e[i];
        if(v==fa[t]||v==son[t]) continue;
        dfs2(v,v);
    }
    return ;
}
void updata(int num){
    seg[num].maxx=max(seg[num<<1].maxx,seg[num<<1|1].maxx);
    seg[num].sum=seg[num<<1].sum+seg[num<<1|1].sum;
    return ;
}
void make_seg(int num,int ll,int rr){
    seg[num].l=ll;seg[num].r=rr;
    if(ll==rr){
        seg[num].maxx=seg[num].sum=val[intr[ll]];
        //易错。intr而不是inseg,线段树中ll点值是ll点在原树内的标号的值
        return ;
    }
    int mid=ll+rr>>1;
    make_seg(num<<1,ll,mid);
    make_seg(num<<1|1,mid+1,rr);
    updata(num);
    return ;
}
int asksum_seg(int num,int ll,int rr){
    if(ll>rr) swap(ll,rr);
    if(seg[num].l>=ll&&seg[num].r<=rr) return seg[num].sum;
    int mid=seg[num].l+seg[num].r>>1;
    int ret=0;
    if(ll<=mid) ret+=asksum_seg(num<<1,ll,rr);
    if(rr>mid) ret+= asksum_seg(num<<1|1,ll,rr);
    return ret;

}
int askmax_seg(int num,int ll,int rr){
    if(ll>rr) swap(ll,rr);
    if(seg[num].l>=ll&&seg[num].r<=rr) return seg[num].maxx;
    int mid=seg[num].l+seg[num].r>>1;
    int ret=-2e9;
    if(ll<=mid) ret=max(ret,askmax_seg(num<<1,ll,rr));
    if(rr>mid)  ret=max(ret,askmax_seg(num<<1|1,ll,rr));
    return ret;

}
int asksum(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(deep[top[y]]>deep[top[x]])//比较top的深度,而非xy的深度。
        swap(x,y);
        ans+=asksum_seg(1,inseg[top[x]],inseg[x]);
        x=fa[top[x]];
    }
    ans+=asksum_seg(1,inseg[x],inseg[y]);
    return ans;
}
int askmax(int x,int y){
    int ans=-2e9;
    while(top[x]!=top[y]){
        if(deep[top[y]]>deep[top[x]])
        swap(x,y);
        ans=max(ans,askmax_seg(1,inseg[top[x]],inseg[x]));
        x=fa[top[x]];
    }
    ans=max(ans,askmax_seg(1,inseg[x],inseg[y]));
    return ans;
}
void change(int num,int x,int v){
    if(seg[num].l==seg[num].r){
        seg[num].sum=v;
        seg[num].maxx=v;
        return ;
    }
    int mid=seg[num].l+seg[num].r>>1;
    if(x<=mid) change(num<<1,x,v);
    else change(num<<1|1,x,v);
    updata(num);
    return ;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d",&a,&b);
        build(a,b);build(b,a);
    }
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    dfs1(1,0);
    dfs2(1,1);
    make_seg(1,1,cnt_seg);
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        string s;
        cin>>s;
        scanf("%d%d",&a,&b);
        if(s=="CHANGE") change(1,inseg[a],b);
        if(s=="QMAX") printf("%d\n",askmax(a,b));
        //易错,这里要写a,b,而非inseg[a],inseg[b],因为重链在原树内,而不是在线段树内,线段树内只有多段连续的区间。
        if(s=="QSUM") printf("%d\n",asksum(a,b));//易错,同上
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值