总结-树链剖分

总结-树链剖分


(今天学会了树链剖分,\(^O^)/)

BZOJ1036为例,题目中要求树上的区间和,最值询问.
若不是在树上的话,很容易想到用线段树来来实现.

树链剖分实质

而树链剖分实际上也就是将树结构剖分成一条一条的链结构,实际上就是区间,以便于数据结构的操作.
就题目而言,若将树剖分成链,那么就很容易再在线段树的基础上维护.

如何剖分?

显然链的条数越少越好,那么对于一个点而言,他只能和其子节点中选择一个分成一条链,其余的另外成链.要使得条数尽可能少,就应当选择节点个数最多的那个子节点,称之为重儿子.其他的轻儿子就另外成链.将节点与其重儿子所形成的边称为重边,其他边称之为轻边.

重链的性质

将全部由重边形成的链称之为重链,可以发现重链和轻边的条数均为 log 级别的.
应为对于一个点而言,若其不为父节点的重儿子,那么其子树结点和必定小于等于总结点数的1/2,那么一直出现新的重链只能有 log 级别次.
且轻边的条数也是 log 级别次,因为每两条重链间夹着一条轻边.

两点间路径的遍历

很多时候用到树链剖分是在树上操作,那么就可能会涉及到两点间距离操作.
以两点x,y间求区间点权和为例:
1.讨论x,y是否在同一条重链上:
若在,直接用数据结构求[x,y]区间和.
若不在,将所在链顶端较深的向上移动,并记录下该点到所在链顶端的区间和.

代码实现

1.找重边

dfs求出子树结点数,取节点数最大的作为重儿子,得到重儿子之后便知道结点的重边.

int fa[maxn],sz[maxn],son[maxn];//fa 父节点数组 sz 子树结点数 son 结点重儿子

void dfs1(int u,int p)//u 当前结点 p 父节点
{
    sz[u]=1;fa[u]=p;son[u]=0;
    for(int i=fir[u];i;i=nxt[i]) if(to[i]!=p) {
        int v=to[i];
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
2.成重链

已经求出了重边,现在要将其连起来.既然要连起来,那么重链上的点肯定要连续,那么尝试将同一重链上的点连续存放,形成多个连续区间,那么就将多条重链连起来了.

int top[maxn],dep[maxn],rnk[maxn],idx[maxn],id;//top 结点所在重链的顶端 dep 结点深度 rnk 新标号映射旧标号 idx 旧标号映射新标号

void dfs2(int u,int t,int d)//u 当前结点 t 结点所在重链顶端 d 结点深度
{
    top[u]=t;dep[u]=d;rnk[idx[u]=++id]=u;
    if(son[u]) dfs2(son[u],t,d+1);//先dfs重儿子,才能保证同重链上的点连续存放
    for(int i=fir[u];i;i=nxt[i])
        if(to[i]!=fa[u]&&to[i]!=son[u]) dfs2(to[i],to[i],d+1);
}
3.两点间路径(以路径点权和为例)
int solve(int x,int y)
{
    int ret=0;
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ret+=query(1,1,N,idx[top[x]],idx[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    ret+=query(1,1,N,idx[x],idx[y],k);
    return ret;
}
4.求lca
int lca(int x,int y)
{
    while(top[x]!=top[y])
        if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];
        else y=fa[top[y]];
    return dep[x]<dep[y]?x:y;
}

相关题目:

1.BZOJ 1036 题目 题解
2.BZOJ 3637 题目 题解
3.POJ 3237 题目 题解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值