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

近来总有人向我安利树剖求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. Ajax的简单实现

    Ajax的实现需要服务器端和客户端配合来实现 下面看服务器端的代码,也就是用php编写的一个后台脚本文件 <?php //设置页面内容,编码格式是utf8 header("Conten ...

  2. 对FreeMarker技术的思考

    依照静态非静态来划分网页分为两种:静态网页和非静态网页,究其优缺点而言,静态网页在用户訪问的时候响应快,可是因为里面的数据是写死的.所以致命的缺陷就是数据不能动态显示.非静态页面(如jsp)数据能够动 ...

  3. 4408: [Fjoi 2016]神秘数

    4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 452  Solved: 273 [Submit][Stat ...

  4. Java程序员从笨鸟到菜鸟之(十五)Html基础积累总结(下)

     本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 一:表格 1.表格的基本语法 <table>...</table> ...

  5. (29)java web的hibernate使用-crud的dao

    1, 做个简单的util public class HibernateUtils { private static SessionFactory sf; static { //加载主要的配置文件 sf ...

  6. mysql数据库引擎InnoDB和MyISAM的区别

    InnoDB支持行级锁和表级锁(默认行级锁),支持事务,外部键等:大量的insert和update更快等.只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁. MyI ...

  7. css3中animation的应用

    1.css3 的相关属性: 相关代码: div { animation-name: myfirst; //动画的名称 animation-duration: 5s; //动画一个周期需要5秒 anim ...

  8. html的书写规范,有哪些注意点

    1.最开始的声明格式与编码格式,注意html5与和html4.0的区别,注意对不同浏览器的渲染作用: 2.<head></head>标签中的相关内容的编写: 3.确保引入的jq ...

  9. nginx、mysql、php等各编译参数查询

    查看nginx编译参数:/usr/local/nginx/sbin/nginx -V 查看apache编译参数:cat /usr/local/apache2/build/config.nice 查看m ...

  10. ping 和 远程桌面 与防火墙的关系

    现象: 初始化服务器时,人们为了方便,常常关闭防火墙,这样做很危险.我们经常遇到问题是,防火墙一旦打开,ping和远程桌面就会有问题. 有时ping不通,但能远程:但有时ping通,又远程不了:有时都 ...