树链剖分

树链剖分

前言:虽然noip基本不考,但我觉得还是多少学一点吧


问题模型

对于树上的某条路径,进行一系列操作(类似线段树上的操作)


实现原理

恰如其名,将树剖分成一段又一段的区间(树链),便于我们进行线段树的操作(树上的线段树操作)。将树分为重链和轻链,重链的dfs序(与其他dfs序不同,树剖的dfs序会优先落在重链上)多为一段连续的区间。因此当解决两点间路径上的查询和修改问题时,可以用类似于LCA(其实区别较大,LCA是通过倍增来跳,而树剖是通过重链来跳)的方法,将两点之间的路径剖分成多条首尾相连的树链,每条树链上的点所在区间是连续的,进而进行线段树的操作。(关于重链,轻链的定义请自行查询)


代码实现

进行两遍dfs,第一遍储存size[],father[],son[](重儿子),dep[]

第二遍储存top[](所在重路径的顶部节点),seg[](在线段树中的位置),rev[](seg[rev[x]]=x)

void dfs1(int u,int father)
{
    ssize[u]=1;
    dep[u]=dep[father]+1;
    fa[u]=father;
    for(int i=fir[u];i;i=nxt[i])
    {
        int v=vv[i];
        if(v==father)continue;
        dfs1(v,u);
        ssize[u]+=ssize[v];
        if(ssize[v]>ssize[son[u]])son[u]=v;
    }
}
void dfs2(int u,int father)
{
    if(son[u])
    {
        seg[son[u]]=++seg[0];
        rev[seg[son[u]]]=son[u];
        top[son[u]]=top[u];
        dfs2(son[u],u);
    }
    for(int i=fir[u];i;i=nxt[i])
    {
        int v=vv[i];
        if(!top[v])
        {
            seg[v]=++seg[0];
            rev[seg[v]]=v;
            top[v]=v;
            dfs2(v,u);
        }
    }
}

当查询两点之间的路径时,通过每次top[]较深的点往上跳,当跳到同一条重链上时,深度较浅的一点即为两点的LCA,再将两点的路径转化为多条重链(跳跃的过程),分别进行区间查询。

void ask(int x,int y)
{
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
        query(1,1,seg[0],seg[fx],seg[x]);
        x=father[fx];fx=top[x];
    }
    if(dep[x]>dep[y])swap(x,y);
    query(1,1,seg[0],seg[x],seg[y]);
}

例题

[Noi2015]软件包管理器

解析:虽然是NOI的题,但却是一道Day1 T2的除了题意比较冗长外的极水的树链剖分裸题。安装软件包就是将根节点到x节点的路径上所有值变为1,卸载软件包就是将x节点和它所有子树节点的值变为0,而x和它的子树节点在树链剖分中恰好是一段连续的区间[seg[x],seg[x]+size[x]-1],然后直接树链剖分做就可以了。

转载于:https://www.cnblogs.com/Akaina/p/11220780.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值