Loading [MathJax]/jax/input/TeX/jax.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >树链剖分 算法学习

树链剖分 算法学习

作者头像
yzxoi
发布于 2022-09-19 04:27:54
发布于 2022-09-19 04:27:54
35300
代码可运行
举报
文章被收录于专栏:OIOI
运行总次数:0
代码可运行

树链剖分 算法学习

树你应该懂的吧o( ̄︶ ̄)o 学习树链剖分之前需要先学习:dfs、线段树(当然大佬们用树状数组代替线段树也可以O(∩_∩)O),据说一名普及+的oier应该都会呀

先来了解树链剖分的用处

Luogu题目传送门 已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
  • 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
  • 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
  • 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

如果直接暴力的话,肯定会TLE(废话)。所以这时候,树链剖分闪亮登场。

什么是树链剖分

一种算法(废话),它通过分轻重边把树分割成很多链,然后利用某种数据结构维护这些链(比如上文提到的线段树、树状数组等)但前提是这种数据结构支持动态修改(你别给我整个RMQ)。本质上是一种暴力算法。 PS:树剖的复杂度约为O(nlog2n)

树链剖分的基本概念

名称

概念

重儿子

父亲节点的所有儿子中子节点数目最多(sz最大)的节点

轻儿子

父亲节点除了重儿子以外的儿子

重边

父亲节点和重儿子连成的边

轻边

父亲节点和轻儿子连成的边

重链

由多条重边连成的路径

轻链

由多条轻边连成的路径

没看懂?没关系,结合下面这张图:(红色的边代表重边,黑色的边代表轻边,红色的节点代表重儿子,黑色的节点代表轻儿子) PS:这里默认树根也是重儿子。

上图的重链有:1-4,3-6。

变量声明

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ll fir[MAXN],nxt[MAXN*2],son[MAXN*2],w[MAXN*2],tot;
struct Node{
    ll sum,tag,l,r,ls,rs;
}a[2*MAXN];
ll root,n,m,r,mod,v[MAXN],cnt,fa[MAXN],dep[MAXN],sz[MAXN],c[MAXN],rk[MAXN],top[MAXN],id[MAXN];

名称

作用

firx

关于x的最后一条边编号

nxtx

关于x的上一条边编号

sonx

x条边的连向

wx

其实没啥用,打着习惯了

ax.ls

编号为x的节点的左儿子

ax.rs

编号为x的节点的右儿子

fax

编号为x的节点的父亲

cx

编号为x的节点的重儿子

rkx

当前dfs标号在树中所对应的节点的编号

topx

编号为x的节点所在链的顶端节点编号

idx

编号为x的节点dfs后的新编号

depx

编号为x的节点的深度

szx

以编号为x的节点为根的子树的节点个数

树链剖分的实现

第一次$dfs$求出每个节点的重儿子、父亲、深度、子树大小。

PS:如果一个点的多个儿子所在子树大小相等且最大,那随便找一个当做它的重儿子就好了,叶节点没有重儿子,非叶节点有且只有一个重儿子。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline void dfs1(ll x,ll f,ll deep){
    fa[x]=f;//该节点的父亲
    dep[x]=deep;//该节点深度
    sz[x]=1;//该节点子树先设置为1(本身)
    for(ll i=fir[x];i;i=nxt[i]){//寻找与该节点相连的边
        ll to=son[i];//该边的另一个节点
        if(to==f) continue ;//如果另一个节点刚好是父亲,那么continue 
        dfs1(to,x,deep+1);//否则dfs该节点,并且父亲为本节点,深度+1
        sz[x]+=sz[to];//子树大小增加
        if(sz[to]>sz[c[x]]) c[x]=to;//重儿子更新(找子树最大的)
    }
}
//主函数调用
dfs1(root,0,1);

操作完以后应该是下图:

第二次$dfs$求出每个节点的链顶端节点、新编号、$dfs$编号对应的节点编号。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline void dfs2(ll x,ll ttop){
    top[x]=ttop;//链顶端编号
    id[x]=++cnt;//新编号(dfs序)
    rk[cnt]=x;//新编号对应节点编号
    if(c[x]!=0) dfs2(c[x],ttop);//如果不是叶子节点,优先dfs重儿子,因为节点与重儿子处在同一重链,所以重儿子的重链顶端还是ttop
    for(ll i=fir[x];i;i=nxt[i]){
        ll to=son[i];
        if(to!=c[x]&&to!=fa[x]) dfs2(to,to);//如果既不是父亲也不是重儿子,那么就是该节点的轻儿子,那么dfs,且该节点的重链顶端为它本身
    }
}
//主函数调用
dfs2(root,root);

操作完以后应该是下图:

线段树等数据结构的维护

接下来就是线段树、树状数组等数据结构的维护了,具体使用哪种数据结构因题目而异,这里提供模板题(上文介绍的题目)所使用的线段树(区间修改、区间询问)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline void pushup(ll x){
    a[x].sum=(a[a[x].ls].sum+a[a[x].rs].sum)%mod;//更新求和
}
inline void build(ll l,ll r,ll x){
    if(l==r){
        a[x].sum=v[rk[l]];//符合所在区间,更新
        a[x].l=a[x].r=l;//l、r更新
        return ;
    }
    ll mid=l+r>>1;//线段树性质
    a[x].ls=cnt++;a[x].rs=cnt++;//左右儿子节点编号
    build(l,mid,a[x].ls);build(mid+1,r,a[x].rs);//分而治之
    a[x].l=a[a[x].ls].l,a[x].r=a[a[x].rs].r;//区间更新
    pushup(x);//sum更新
}
inline ll len(ll x){
    return a[x].r-a[x].l+1;//该区间的节点数量
}
inline void pushdown(ll x){
    if(a[x].tag!=0){//如果有lazy tag
        a[a[x].ls].tag+=a[x].tag;a[a[x].rs].tag+=a[x].tag;//向左右儿子传递
        a[a[x].ls].tag%=mod;a[a[x].rs].tag%=mod;
        a[a[x].ls].sum+=a[x].tag*len(a[x].ls);a[a[x].rs].sum+=a[x].tag*len(a[x].rs);//左右儿子更新
        a[a[x].ls].sum%=mod;a[a[x].rs].sum%=mod;
        a[x].tag=0;//lazy tag取消
    }
}
inline void update(ll l,ll r,ll c,ll x){
    if(a[x].l>=l&&a[x].r<=r){
        a[x].tag+=c;a[x].tag%=mod;//修改lazy tag
        a[x].sum+=len(x)*c;a[x].sum%=mod;//修改sum
        return ;
    }
    pushdown(x);//标记下传
    ll mid=a[x].l+a[x].r>>1;
    if(mid>=l) update(l,r,c,a[x].ls);//分而治之
    if(mid<r) update(l,r,c,a[x].rs);
    pushup(x);//更新sum
}
inline ll query(ll l,ll r,ll x){
    if(a[x].l>=l&&a[x].r<=r) return a[x].sum;//如果符合在本区间内,那么return
    pushdown(x);//标记下传
    ll mid=a[x].l+a[x].r>>1,ss=0;
    if(mid>=l) ss+=query(l,r,a[x].ls);ss%=mod;//分而治之
    if(mid<r) ss+=query(l,r,a[x].rs);ss%=mod;
    return ss;//返回
}
//主函数调用(根据上文题目)
cnt=0;build(1,n,root=cnt++);
update(id[x],id[x]+sz[x]-1,y,root);
query(id[x],id[x]+sz[x]-1,root);

根据题目需要添加操作

就比如上文的题目中还要求的操作:

  • 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
  • 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和

与操作3、操作4不同,这里要求的是一条路径上的节点,而没有告诉我们节点的编号,所以,我们这时要求出节点编号:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline ll Query(ll x,ll y){
    ll res=0;
    while(top[x]!=top[y]){//若两点不再同一条链上
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        res+=query(id[top[x]],id[x],root);//ans更新
        res%=mod;
        x=fa[top[x]];//让x向上爬(与倍增思想类似,但有时复杂度更低)
    }
    if(id[x]>id[y]) swap(x,y);
    res+=query(id[x],id[y],root);//在同一条链,跳到同一点,ans更新
    res%=mod;
    return res;
}
inline void Update(ll x,ll y,ll c){
    while(top[x]!=top[y]){//两点不在同一条链
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        update(id[top[x]],id[x],c,root);//更新
        x=fa[top[x]];//让x向上爬
    }
    if(id[x]>id[y]) swap(x,y);
    update(id[x],id[y],c,root);//在同一链,跳到同一点,更新
}

当然,还有一个操作是非常常用的,那就是求lca(最近公共祖先)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline ll lca(ll x,ll y){
    while(top[x]!=top[y]){//两点不在同一条链上肯定没有公共祖先
        if(dep[top[x]]>=dep[top[y]])x=fa[top[x]];//让深度低的点向上爬,x向上爬
        else y=fa[top[y]];//y向上爬
    }
    return dep[x]<dep[y]?x:y;//取深度低的点
}

模板题代码

对对对,就是上文提到的题目。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<bits/stdc++.h>
#define MAXN 200010
#define ll long long
using namespace std;
ll fir[MAXN],nxt[MAXN*2],son[MAXN*2],w[MAXN*2],tot;
struct Node{
    ll sum,tag,l,r,ls,rs;
}a[2*MAXN];
ll root,n,m,r,mod,v[MAXN],cnt,fa[MAXN],dep[MAXN],sz[MAXN],c[MAXN],rk[MAXN],top[MAXN],id[MAXN];
inline void dfs1(ll x,ll f,ll deep){
    fa[x]=f;
    dep[x]=deep;
    sz[x]=1;
    for(ll i=fir[x];i;i=nxt[i]){
        ll to=son[i];
        if(to==f) continue ;
        dfs1(to,x,deep+1);
        sz[x]+=sz[to];
        if(sz[to]>sz[c[x]]) c[x]=to;
    }
}
inline void dfs2(ll x,ll ttop){
    top[x]=ttop;
    id[x]=++cnt;
    rk[cnt]=x;
    if(c[x]!=0) dfs2(c[x],ttop);
    for(ll i=fir[x];i;i=nxt[i]){
        ll to=son[i];
        if(to!=c[x]&&to!=fa[x]) dfs2(to,to);
    }
}
inline void pushup(ll x){
    a[x].sum=(a[a[x].ls].sum+a[a[x].rs].sum)%mod;
}
inline void build(ll l,ll r,ll x){
    if(l==r){
        a[x].sum=v[rk[l]];
        a[x].l=a[x].r=l;
        return ;
    }
    ll mid=l+r>>1;
    a[x].ls=cnt++;a[x].rs=cnt++;
    build(l,mid,a[x].ls);build(mid+1,r,a[x].rs);
    a[x].l=a[a[x].ls].l,a[x].r=a[a[x].rs].r;
    pushup(x);
}
inline ll len(ll x){
    return a[x].r-a[x].l+1;
}
inline void pushdown(ll x){
    if(a[x].tag!=0){
        a[a[x].ls].tag+=a[x].tag;a[a[x].rs].tag+=a[x].tag;
        a[a[x].ls].tag%=mod;a[a[x].rs].tag%=mod;
        a[a[x].ls].sum+=a[x].tag*len(a[x].ls);a[a[x].rs].sum+=a[x].tag*len(a[x].rs);
        a[a[x].ls].sum%=mod;a[a[x].rs].sum%=mod;
        a[x].tag=0;
    }
}
inline void update(ll l,ll r,ll c,ll x){
    if(a[x].l>=l&&a[x].r<=r){
        a[x].tag+=c;a[x].tag%=mod;
        a[x].sum+=len(x)*c;a[x].sum%=mod;
        return ;
    }
    pushdown(x);
    ll mid=a[x].l+a[x].r>>1;
    if(mid>=l) update(l,r,c,a[x].ls);
    if(mid<r) update(l,r,c,a[x].rs);
    pushup(x);
}
inline ll lca(ll x,ll 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;
}
inline ll query(ll l,ll r,ll x){
    if(a[x].l>=l&&a[x].r<=r) return a[x].sum;
    pushdown(x);
    ll mid=a[x].l+a[x].r>>1,ss=0;
    if(mid>=l) ss+=query(l,r,a[x].ls);ss%=mod;
    if(mid<r) ss+=query(l,r,a[x].rs);ss%=mod;
    return ss;
}
inline ll Query(ll x,ll y){
    ll res=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        res+=query(id[top[x]],id[x],root);
        res%=mod;
        x=fa[top[x]];
    }
    if(id[x]>id[y]) swap(x,y);
    res+=query(id[x],id[y],root);
    res%=mod;
    return res;
}
inline void Update(ll x,ll y,ll c){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        update(id[top[x]],id[x],c,root);
        x=fa[top[x]];
    }
    if(id[x]>id[y]) swap(x,y);
    update(id[x],id[y],c,root);
}
inline ll read(){
    char ch=getchar();ll res=0,f=1;
    while(ch<'0'ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
    return res*f;
}
inline void write(ll x){
    if(x<10) putchar(x+'0');
    else{
        write(x/10);
        putchar(x%10+'0');
    }
}
inline void add(ll x,ll y){
    ++tot;
    son[tot]=y;
    nxt[tot]=fir[x];
    fir[x]=tot;
}
int main(){
    n=read();m=read();r=read();mod=read();
    for(ll i=1;i<=n;i++) v[i]=read();
    for(ll x,y,i=1;i<n;i++){
        x=read(),y=read();
        add(x,y);add(y,x);
    }
    cnt=0;dfs1(r,0,1);
    dfs2(r,r);
    cnt=0;build(1,n,root=cnt++);
    for(ll op,x,y,k,i=1;i<=m;i++){
        op=read();
        if(op==1){
            x=read();y=read();k=read();
            Update(x,y,k);
        }else if(op==2){
            x=read();y=read();
            write(Query(x,y));putchar('\n');
        }else if(op==3){
            x=read();y=read();
            update(id[x],id[x]+sz[x]-1,y,root);
        }else if(op==4){
            x=read();
            write(query(id[x],id[x]+sz[x]-1,root));putchar('\n');
        }
    }
    return 0;
}

完美撒花✿✿ヽ(°▽°)ノ✿

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-02-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
洛谷P3313 [SDOI2014]旅行(树链剖分 动态开节点线段树)
题意 题目链接 Sol 树链剖分板子 + 动态开节点线段树板子 #include<bits/stdc++.h> #define Pair pair<int, int> #define MP(x, y) make_pair(x, y) #define fi first #define se second //#define int long long #define LL long long #define Fin(x) {freopen(#x".in","r",stdin);} #define Fou
attack
2019/03/01
5210
【 Gym - 101138J 】Valentina and the Gift Tree(树链剖分)
n个节点的一棵树,每个节点的权值为g,q个询问,树上的节点U-V,求U到V的路径的最大子段和。
饶文津
2020/06/02
3000
洛谷P3038 [USACO11DEC]牧草种植Grass Planting
题目描述 Farmer John has N barren pastures (2 <= N <= 100,000) connected by N-1 bidirectional roads, such that there is exactly one path between any two pastures. Bessie, a cow who loves her grazing time, often complains about how there is no grass on the road
attack
2018/04/11
6610
洛谷P3038 [USACO11DEC]牧草种植Grass Planting
YbtOJ 774「分块算法」奇妙的树
假设 i 号点的父节点为 f_i(方便起见认为 f_1=0),小 A 发现这棵树非常奇妙,它满足一个特殊的性质:对于任意整数 i\in[2,n],满足 f_{i-1}\le f_i < i
yzxoi
2022/09/19
4380
洛谷P3248 [HNOI2016]树(主席树 倍增 )
题意 题目链接 Sol 从上午九点淦到现在qwq 思路比较简单,就是把每次加入的一坨点看成一个,然后直接倍增搞。。 然后慢慢调就可以了。。。 最后数量级会到达\(10^{10}\),所以应该开long long #include<bits/stdc++.h> #define Pair pair<LL, LL> #define MP make_pair #define fi first #define se second #define LL long long #define int long long
attack
2019/03/04
3140
[Atcoder][CF]简单题选做练习笔记 2
第一个式子,要求 p_i \equiv 0 \pmod 3,第二个式子要求 p_i \equiv 1 \pmod 3 且 p_j \equiv 2 \pmod 3 或者反过来。
Clouder0
2022/09/23
3820
CF GYM102759 I. Query On A Tree 17
给定一棵以 1​ 为根节点的拥有 N​ 个节点的带点权树,初始时每个点的点权均为 0​,有 Q​ 次操作,每次操作有两种类型:
yzxoi
2022/09/19
5610
bzoj 3653 谈笑风生 题解
设a和b为T 中的两个不同节点。如果a是b的祖先,那么称“a比b不知道高明到哪里去了”。
yzxoi
2022/09/19
5270
牛客NOIP提高组R1 C保护(主席树)
考虑一个点x,什么时候军队对它有贡献,肯定是u或v在他的子树内,且lca在他的子树外
attack
2019/01/30
3040
BZOJ3672: [Noi2014]购票(dp 斜率优化 点分治 二分 凸包)
嘿嘿,这道题的点分治不同于一般的点分治。正常的点分治思路大概是先统计过重心的,再递归下去
attack
2019/01/03
3780
轻重链剖分练习笔记
轻重链剖分,常被称为树链剖分,是一种常用的维护树上信息的算法。 它以子树大小为依据,将节点划分为重儿子与轻儿子,从而使整棵树被剖分成若干条重链。 每个轻儿子都是一条重链的开始。一个节点只在一条重链上。 从树上任意一点到根节点,最多经过 \log n 条连续的链。 利用这样的特殊性质,可以解决许多问题。 由于是练习笔记,本文不再赘述概念有关内容。
Clouder0
2022/09/23
3960
树链剖分详解
前言 树链剖分是什么? 树链剖分,说白了就是一种让你代码不得不强行增加1k的数据结构-dms 个人理解:+1:joy: 有什么用? 证明出题人非常毒瘤 可以非常友(bao)好(li)的解决一些树上问题:grimacing: (友情提示:学树链剖分之前请先掌握线段树) 核心思想 树链剖分的思想比较神奇 它的思想是:把一棵树拆成若干个不相交的链,然后用一些数据结构去维护这些链 那么问题来了  如何把树拆成链? 首先明确一些定义 重儿子:该节点的子树中,节点个数最多的子树的根节点(也就是和该节点相连的点)
attack
2018/04/11
1K0
树链剖分详解
BZOJ3083: 遥远的国度(树链剖分)
以下图片来自(https://blog.csdn.net/lcomyn/article/details/45718295)
attack
2018/07/27
3190
BZOJ3083: 遥远的国度(树链剖分)
BZOJ4196: [Noi2015]软件包管理器(树链剖分 线段树)
Linux用户和OSX用户一定对软件包管理器不会陌生。通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置。Debian/Ubuntu使用的apt-get,Fedora/CentOS使用的yum,以及OSX下可用的homebrew都是优秀的软件包管理器。
attack
2018/07/27
3550
BZOJ4196: [Noi2015]软件包管理器(树链剖分 线段树)
洛谷P3178 [HAOI2015]树上操作
题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:操作 1 :把某个节点 x 的点权增加 a 。操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。操作 3 :询问某个节点 x 到根的路径中所有点的点权和。 输入输出格式 输入格式: 第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。再接下来 M 行,每
attack
2018/04/11
6420
HDU 5458 Stability(树链剖分+ 并查集)
2.如果删除(u, v)间的一条边可使其不连通,找出这样的边的个数,就是找(u, v)间桥的个数
用户2965768
2019/08/29
3290
洛谷P3384 【模板】树链剖分
题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号
attack
2018/04/11
6790
洛谷P3384 【模板】树链剖分
P2590 [ZJOI2008]树的统计
题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入输出格式 输入格式: 输入文件的第一行为一个整数n,表示节点的个数。 接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相
attack
2018/04/12
5150
树链剖分简单分析及模板(杂谈)
这几天学习了一下树链剖分,顺便写一下我的理解、 早上看了一下别人的讲解,云里雾里,终于算是搞懂了、 树链剖分是解决在树上进行插点问线,插线问点等一系列树上的问题 假如现在给你一棵树,然后没两条边之间有一条权值,有一些操作,1:x---y之间的最大权值是多少,2:改变x---y之间的权值 当前这样的操作有很多,如果直接用暴力的方法的话肯定不行,那么就要想一个好的方法,我们可以想一下能不能借助线段树解决,能不能想一种方法对树上的边进行编号,然后就变成区间了。那么我们就可以在线段树上进行操作了,树链剖分就是这样
Angel_Kitty
2018/04/08
6830
hdu3966_树链剖分
最近在强化知识点深度,发现树链剖分不是很会写了。 回顾一下修改操作: 若两个点在同一条链上,则直接修改这段区间。 若不在同一条链上,修改深度较大的点到其链顶端的区间,同时将这个点变为他所在链顶端的父亲,循环操作直到这两个点在同一条链上,就可以用上一种方法了。 没有用LCA写是因为以前被坑过,不但没有这种方法好写,效率也不太让人满意。 主要是对第二种情况如何写有所遗忘,写道模版再给自己提个醒。 #include<iostream> #include<cstdio> #include<cstdlib> #inc
triplebee
2018/01/12
5200
相关推荐
洛谷P3313 [SDOI2014]旅行(树链剖分 动态开节点线段树)
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文