【笔记+模板】树链剖分

来源:互联网 发布:产品图册制作软件 编辑:程序博客网 时间:2024/05/29 09:32

  • 定义
    • 定义
    • 解释
  • 算法流程
    • 重链剖分
    • 维护重链
    • 修改查询
  • 模板

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

定义

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;}