先%一发机房各路祖传树剖大师%%%。

近来总有人向我安利树剖求LCA,然鹅我还是最爱树上倍增。然鹅又发现近年一些题目(如天天爱跑步、运输计划等在树上进行操作的题目),我有把树转化为一条链求解的思路,但是不知道怎么实现。于是还是学了树链剖分(真香),就权当打暴力的工具了。其实主要是学习它的思想,而它实际包含的知识(线段树(大多情况用线段树,理论上应该还能用其他数据结构维护)、dfs序与时间戳、树的遍历)比较基础,只要把他们掌握,学习树剖就不难了。讲真树剖可能是我学的最快的知识。


主要思想:划分轻重链,把树上的某条路径化为一条链,再用数据结构维护。(把一棵树拆成若干互不相交的链)

树剖中的一些概念:

  这里可详见@communist dalao的解释部分

可见,树剖中我们需要记录很多的量,于是就有了我们的第一个核心算法:两遍dfs,求出所有信息!

通常我们第一遍dfs求出d[](深度),f[](父节点),size[](子树大小),son[](重儿子)。

void dfs1(int u,int fa,int dep)
{//第一遍dfs 预处理出f[]/d[]/son[]/size[]
f[u]=fa;
d[u]=dep;
size[u]=;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs1(v,u,dep+);
size[u]+=size[v];
if(size[v]>size[son[u]])
son[u]=v;
}
}

第二遍我们在第一遍的基础上继续求出其他量。top[](当前节点所在链的最顶层节点),id[](节点的新编号,可理解为时间戳),rk[](保存dfs标号在树上的具体节点,实际操作可为点权)

这里id与rk的关系有点像时间戳与dfs序,但不完全是,emm可以感性理解下。(时间戳与dfs序,他们的联系具体在这里探讨过。)

至于为什么要引入他们,因为我们希望一条重链在数据结构(如线段树)上的排列分布是连续的,这样我们才好维护他们。

void dfs2(int u,int t)
{//第二遍dfs 预处理出id[]/rk[]/top[]
top[u]=t;
id[u]=++cnt;
rk[cnt]=val[u];
if(!son[u]) return ;//找到了叶子
//通过优先进入重儿子来保证一条重链上各节点dfs序连续
dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==son[u]||v==f[u]) continue;
dfs2(v,v);//在轻链上 top为本身
}
}

有了这些操作,我们就可以进行线段树的维护了。这里的线段树中,节点标号用的是我们刚映射好的时间戳,权值是节点的点权,重链上的节点在线段树上标号连续。(注意我们的rk数组,建树时用的是它而不是初始点权。)

之后我们就按线段树规矩建树、修改、查询即可。这是一个相对独立的部分。


以上便是树链剖分的基本操作,我们以LuoguP3384【模板】树链剖分为例,讲解一下树剖的具体食用方法。

题目要求我们维护:

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

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

操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z

操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

慢慢分析。在操作1中,我们首先需要让x,y到同一个重链上,但是在到达之前,我们也需要记录下。于是就有了

void treeadd(int x,int y,int w)
{
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]]) swap(x,y);
change(,id[top[x]],id[x],w);
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);
change(,id[x],id[y],w);
}

操作2原理相似。因为这部分我讲的不是很好==。我是看这位大神的blog看懂的,这里就甩链接了...。

ll treeask(int x,int y)
{
ll ans=;
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]]) swap(x,y);
(ans+=ask(,id[top[x]],id[x]))%=moder;
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);
(ans+=ask(,id[x],id[y]))%=moder;
return ans;
}

至于操作3.4,其实最简单了qwq。我们直接在线段树上操作就行了。

综上,我们解决了第一道树剖题。

 #include<cstdio>
#include<algorithm>
#define maxn 100090 using namespace std;
typedef long long ll; int n,m,root,moder,tot,cnt;
int head[maxn],size[maxn],d[maxn],f[maxn],son[maxn],val[maxn];
int top[maxn],id[maxn],rk[maxn];
struct node{
int to,next;
}edge[maxn*];
struct SegmentTree{
int l,r;
ll w,lazy;
}t[maxn*]; void add(int x,int y)
{
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
} void dfs1(int u,int fa,int dep)
{//第一遍dfs 预处理出f[]/d[]/son[]/size[]
f[u]=fa;
d[u]=dep;
size[u]=;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs1(v,u,dep+);
size[u]+=size[v];
if(size[v]>size[son[u]])
son[u]=v;
}
} void dfs2(int u,int t)
{//第二遍dfs 预处理出id[]/rk[]/top[]
top[u]=t;
id[u]=++cnt;
rk[cnt]=val[u];
if(!son[u]) return ;//找到了叶子
//通过优先进入重儿子来保证一条重链上各节点dfs序连续
dfs2(son[u],t);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==son[u]||v==f[u]) continue;
dfs2(v,v);//在轻链上 top为本身
}
} void update(int p)
{
if(t[p].l==t[p].r) return ;
if(!t[p].lazy) return ;
t[p*].w+=t[p].lazy*(t[p*].r-t[p*].l+);
t[p*+].w+=t[p].lazy*(t[p*+].r-t[p*+].l+);
t[p*].lazy+=t[p].lazy;
t[p*+].lazy+=t[p].lazy;
t[p].lazy=;
} void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if(l==r)
{
t[p].w=rk[l];
return ;
}
int mid=(l+r)>>;
build(p*,l,mid);
build(p*+,mid+,r);
(t[p].w=t[p*].w+t[p*+].w)%=moder;
} void change(int p,int l,int r,int x)
{
update(p);
if(t[p].l==l&&t[p].r==r)
{
t[p].w+=x*(r-l+);
t[p].lazy+=x;
return ;
}
int mid=(t[p].l+t[p].r)>>;
if(l>mid) change(p*+,l,r,x);
else if(r<=mid) change(p*,l,r,x);
else change(p*,l,mid,x),change(p*+,mid+,r,x);
(t[p].w=t[p*].w+t[p*+].w)%=moder;
} ll ask(int p,int l,int r)
{
update(p);
if(t[p].l==l&&t[p].r==r) return (t[p].w)%moder;
int mid=(t[p].l+t[p].r)>>;
if(l>mid) return (ask(p*+,l,r))%moder;
else if(r<=mid) return (ask(p*,l,r))%moder;
else return (ask(p*,l,mid)+ask(p*+,mid+,r))%moder;
} ll treeask(int x,int y)
{
ll ans=;
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]]) swap(x,y);
(ans+=ask(,id[top[x]],id[x]))%=moder;
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);
(ans+=ask(,id[x],id[y]))%=moder;
return ans;
} void treeadd(int x,int y,int w)
{
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]]) swap(x,y);
change(,id[top[x]],id[x],w);
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);
change(,id[x],id[y],w);
} int main()
{
scanf("%d%d%d%d",&n,&m,&root,&moder);
for(int i=;i<=n;i++) scanf("%d",&val[i]);
for(int i=;i<=n-;i++)
{
int x=,y=;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs1(root,,);
dfs2(root,root);
build(,,n);
while(m--)
{
int opt=;
scanf("%d",&opt);
if(opt==)
{
int x=,y=,z=;
scanf("%d%d%d",&x,&y,&z);
treeadd(x,y,z);
}
else if(opt==)
{
int x=,y=;
scanf("%d%d",&x,&y);
printf("%lld\n",treeask(x,y));
}
else if(opt==)
{
int x=,y=;
scanf("%d%d",&x,&y);
change(,id[x],id[x]+size[x]-,y);
}
else if(opt==)
{
int x=;
scanf("%d",&x);
printf("%lld\n",ask(,id[x],id[x]+size[x]-)%moder);
}
}
return ;
}

Update:2018/10/3

早上来做了一道树链剖分...本想一次过的结果卡到九点...。

原因:单点修改的时候用的是x而不是id[x],查询最大值的时候因为忽略了还有负数,所以初始值设的是0,而应该是负无穷。

题目链接

Update:2018/10/6

今天又做了一道板子题==

依然改了很久==

没有什么困难的操作,有一个单点修改。

开始我是想纯用之前的单点修改不带懒标记的,后来爆零了==

改成区间修改左右端点相同的情况,再开着longlong就能A了==。

百思不得其解,难道只能化为区间修改的特殊情况了么==。

后来在金涛的指点下,发现单点修改下传一个懒标记就行了,就能A了。原因不太明...。

Update:2018-10-31

今日份敲模板的错误:

//size[x]=1
//chushizhi rk[p]->rk[l]
//update 一直在lazy val操作假的

树链剖分学习笔记 By cellur925的更多相关文章

  1. 蒟蒻的长链剖分学习笔记(例题:HOTEL加强版、重建计划)

    长链剖分学习笔记 说到树的链剖,大多数人都会首先想到重链剖分.的确,目前重链剖分在OI中有更加多样化的应用,但它大多时候是替代不了长链剖分的. 重链剖分是把size最大的儿子当成重儿子,顾名思义长链剖 ...

  2. 树链剖分学习&BZOJ1036

    题目传送门 树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组.SBT.SPLAY.线段树等)来维护每一条链. ...

  3. BZOJ 4543 2016北京集训测试赛(二)Problem B: thr 既 长链剖分学习笔记

    Solution 这题的解法很妙啊... 考虑这三个点可能的形态: 令它们的重心为距离到这三个点都相同的节点, 则其中两个点分别在重心的两棵子树中, 且到重心的距离相等; 第三个点可能在重心的一棵不同 ...

  4. LightOJ 1348 (树链剖分 + 线段树(树状数组))

    题目 Link 分析 典型的树链剖分题, 树链剖分学习资料 Code #include <bits/stdc++.h> using namespace std; const int max ...

  5. 【SPOJ-QTREE】树链剖分

    树链剖分学习 https://blog.csdn.net/u013368721/article/details/39734871 https://www.cnblogs.com/George1994/ ...

  6. BZOJ 1036: [ZJOI2008]树的统计Count [树链剖分]【学习笔记】

    1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 14302  Solved: 5779[Submit ...

  7. 树链剖分 树剖求lca 学习笔记

    树链剖分 顾名思义,就是把一课时分成若干条链,使得它可以用数据结构(例如线段树)来维护 一些定义: 重儿子:子树最大的儿子 轻儿子:除了重儿子以外的儿子 重边:父节点与重儿子组成的边 轻边:除重边以外 ...

  8. 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分

    树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...

  9. 【算法学习】【洛谷】树链剖分 & P3384 【模板】树链剖分 P2146 软件包管理器

    刚学的好玩算法,AC2题,非常开心. 其实很早就有教过,以前以为很难就没有学,现在发现其实很简单也很有用. 更重要的是我很好调试,两题都是几乎一遍过的. 介绍树链剖分前,先确保已经学会以下基本技巧: ...

随机推荐

  1. 为Joomla 2.5的连续插入多幅图像添加便捷方式

    用过Joomla 2.5的朋友应该都知道插入很多图像时是比較麻烦的.点了文章以下的图片button,它会弹出个div,让你选择图片,每选一张.div就关闭. 再选第二张的时候,它又要你又一次选择文件夹 ...

  2. Codefoces 432 C. Prime Swaps(水)

    思路:从前往后想将1调整好,在调整2....这样平均每次有五次机会调整,并且有相当一部分可能都用不到五次,能够一试.ac 代码: #include<iostream> #include&l ...

  3. 2015-03-12---外观模式,建造者模式(附代码),观察者模式(附代码),boost库应用

    今天白天主要看了boost库的应用,主要是经常使用的一些库,array,bind,function,regex,thread,unordered,ref,smartpointers库,晚上看了看设计模 ...

  4. GitHub 上值得关注的 iOS 开源项目

    GitHub 上值得关注的 iOS 开源项目 原文链接:http://www.jianshu.com/p/e5dfe1a09611 GitHub 上值得关注的 iOS 开源项目 —— 由 红旗下的蛋  ...

  5. scala快速学习笔记(一):变量函数,操作符,基本类型

    为了用spark,先学下scala. 参考教程:http://meetfp.com/zh/scala-basic doc查询:http://docs.scala-lang.org 其它资料:http: ...

  6. hadoop 集群搭建 配置 spark yarn 对效率的提升永无止境

    [手动验证:任意2个节点间是否实现 双向 ssh免密登录] 弄懂通信原理和集群的容错性 任意2个节点间实现双向 ssh免密登录,默认在~目录下 [实现上步后,在其中任一节点安装\配置hadoop后,可 ...

  7. virtualbox创建centos7虚拟机

    安装Virtualbox 下载安装: 直接到官网上下载,https://www.virtualbox.org/wiki/Downloads 然后一键傻瓜式的安装即可. 设置默认虚拟电脑位置: 管理=& ...

  8. Immutable学习及 React 中的实践

    为什么用immutable.js呢.有了immutable.js可以大大提升react的性能. JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原 ...

  9. super究竟是个啥?

    引子: 一直以为oc的super跟java中的super是一回事,没有去深究它的本质,直到工作的时候遇到一个并不能按我的理解能解释的情况. 剖析: 在此之前先看一段代码: 有两个类 SuperClas ...

  10. 关于div li 等标签之间自带间距

    可以用float来清除标签之间的间距. ps :ul使用font-size:0 唯一的缺点就是要再次设置LI的font-size