[toc]
本文会从链剖讲起,涉及其变形进阶,之后也会讲到DFS序,树上分块,块状树,点分治与边分治,LCT,仙人掌与仙人球,伪top_tree(AAA树),top_tree。
本文重点讲述思维,至于例题虽会有涉及,但还是请读者自行百度(毕竟OI是不缺题的。。。。)
详略在文章中体现,如有错误,请各位读者指正。

从树链剖分讲起

其实我是先会LCT,再会链剖的,所以说比较尴尬。。。
但是自我学习了链剖后,我发现链剖给予了树上操作无限的可能性。

长链与重链

重链剖分在OI中很常见,所以这里只放出模板的博客:
感谢博主——Sdchr
长链剖分比较冷门,大部分使用在O(1)O(1)查询第k级祖先,与重链剖分相对的,就是把size的处理变为deep的处理。
这里也有份比较详细的blog: 感谢博主——ZZQ
这里的长链与重链的关系有些像平衡树中的重量平衡与深度平衡,各有特点,相互弥补在树上操作的不足。
重点即是重链与轻链的交替穿插,使得部分信息能够在树中快速上传,将树变得有序化,层次化就是树链剖分的本质。
长链剖分的题除上述提到外还有 BZOJ1785、BZOJ4543。
讲重点:
1.为什么要学树链剖分?
A:因为要考,lct的常数太大。链剖可以快速地与其他数据结构相关联,易出题。
2.如何学习链剖?
A:做题,做与其他数据结构、算法结合的题,特别是对于链剖在各类的树上运用。链剖模板很简单,但是如何运用是一个需要深度思考的问题。遇到链剖题千万不要裸上LCT水掉,当遇上卡常类、与一些高级算法结合类的题时就会无从下手。(说的就是blog主。。。)
3.学习链剖要到什么程度?
A:模板10~15min,重要的是快判断题目可以用链剖解决,并且知道如何解决(说白了,就是多刷点题)。

上干货(咦?长链剖分都不算干货?)
好吧。。补充长链剖分模板中的核心代码

int fa[N],depth[N],Dep[N],top[N],son[N],dfn[N],cnt;
inline void dfs(int u,int f){Dep[u]=depth[u]=depth[fa]+1,fa[u]=f;for(int p=head[u];p;p=G[p].next)if(V!=f)dfs(V,u),Dep[u]=max(Dep[u],Dep[V]);
}
inline void ask(int u,int fa,int z){dfn[u]=++cnt,top[u]=z,son[u]=0;int maxv=0;for(int p=head[u];p;p=G[p].next)if(V!=fa&&Dep[V]>maxv)maxv=Dep[son[u]=V];if(son[u])ask(son[u],u,z);for(int p=head[u];p;p=G[p].next)if(V!=fa&&V!=son[u])ask(V,u,V);
}

可以看见size不见了只多出来一个新的dep。剩下的就靠自己领悟了。

合并!

从刘rj的紫书上看到了有关树链剖分的合并,详情可以参考
%%%——idy002学长大佬。
有序链剖概念的提出给予我们一扇新世界的大门
见例题T3
link操作神来之笔,但是反过来说,难道寻常链剖做不到有序吗?不见得吧,用if特判或者再建立一棵倒着的树链剖分不就可以了吗?方法有很多不见得固定死思维,只要算法有正确性,复杂度合适,那么勇敢创新是值得借鉴的。

tips

链剖小技巧:
1.求LCA,虽然理论复杂度比倍增高,但是实际效果令人满意,链剖的理论常数基本不大于1,实际贴近于12\frac{1}{2},所以LCA用它在线查询是很不错的。
2.长链、重链一起上,反正都要用到dfs序,而且步骤也简单只需追加一个数组即可,当你无法判定是重链还是长链时,这样很稳。

一般来说,裸的链剖是考不出什么花样的,所以学会套用数据结构才是灵活掌握链剖的表现。

过渡Ⅰ

链剖就这样结束了吧,毕竟大家都有过了解,并且也都学习过,接下来是一道裸题,轻喷。
BZOJ1036
没错,是一道已经做烂的经典题目,什么树链剖分+线段树就可以快速水过,详情请见hzwer(%%%)的代码,里面还有一份很清新的LCT的方法,这些都是极好的。
接下来,就是重点。为什么博主谈到了这道题,那是因为与接下来的块状树有关。

块状树(优秀的暴力)

一般谈到块状树,例题举例便是Gty的超级妹子树之类的,会使人感觉块状树引用范围太少,比不上链剖(虽然这是事实),块状树的应用实际上过更加广泛,更加灵活。
所以上面那道BZOJ1036便是一个例子。
这次不矫情,直接上code:

/*************************************************Problem: 1036User: lemonoilLanguage: C++Result: AcceptedTime:2852 msMemory:4512 kb
****************************************************/#include<algorithm>
#include<cstdio>
#include<cmath>
const int maxn=30000,inf=~0U>>1;
using namespace std;
char cmd[10];
struct data{int to,next;
}E[maxn<<1],Et[maxn<<1];
int n,q,s,t,x,y,w[maxn],sqrtn,deep[maxn],fa[maxn];
int belong[maxn],M[maxn],sz[maxn],head[maxn],tail[maxn],tot,cnt;
void addedge(int x,int y){E[++tot].to=y,E[tot].next=head[x],head[x]=tot;
}
void insert(int x,int y){Et[++cnt].to=y,Et[cnt].next=tail[x],tail[x]=cnt;
}
void dfsf(int t,int f,int d){deep[t]=d,fa[t]=f;int tmp=belong[t];for(register int e=head[t];e;e=E[e].next)if(E[e].to!=f){if(sz[tmp]++<sqrtn)insert(t,E[e].to),belong[E[e].to]=tmp;dfsf(E[e].to,t,d+1);}
}
void dfss(int t,int s,int m){sz[t]=s+=w[t];M[t]=m=max(m,w[t]);for(register int e=tail[t];e;e=Et[e].next)dfss(Et[e].to,s,m);
}
void change(int t,int u){w[t]=u;if(t==belong[t])dfss(t,0,-inf);else dfss(t,sz[fa[t]],M[fa[t]]);
}
void query(int a,int b,int&s,int&m){s=0;m=-inf;while(a!=b){if(deep[a]<deep[b])swap(a,b);if(belong[a]==belong[b])s+=w[a],m=max(m,w[a]),a=fa[a];else{if(deep[belong[a]]<deep[belong[b]])swap(a,b);s+=sz[a],m=max(m,M[a]),a=fa[belong[a]];}}m=max(m,w[a]);s+=w[a];
}
inline void read(int &res){static char ch;int flag=1;while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;res=ch-48;while((ch=getchar())>='0'&&ch<='9')res=res*10+ch-48;res*=flag;
}
int main(){read(n),sqrtn=sqrt(n)+1;for(register int i=0;i<n-1;i++)read(s),read(t),--s,--t,addedge(s,t),addedge(t,s);for(register int i=0;i<n;i++)read(w[i]),belong[i]=i;dfsf(0,-1,0),read(q);for(register int i=0;i<n;i++)if(belong[i]==i)dfss(i,0,-inf);for(register int i=0;i<q;i++){scanf("%s",cmd);read(s),read(t);if(cmd[0]=='C')change(s-1,t);else{query(s-1,t-1,x,y);if(cmd[1]=='S')printf("%d\n",x);else printf("%d\n",y);}}return 0;
}

直观感受就是简洁暴力,效率也是极其高的,是链剖的级别。但是时间复杂度理论却为O(nn√)O(n\sqrt n)只能说明常数竟然为链剖级别!!
块状树的理论在这里 WJMZBMR(%%%)
就像论文里描述的,块状树目前只能解决单点、链的问题,针对子树问题就比较尴尬了。

博主不自量力,正在研究块状树对子树问题的解决方法。(多半是借助其他数据结构。。。)

建块的方法:Dfs一下,对每个结点只要其属于的块大小不超过SqrtN,就把其孩子加入其属于的块,否则其孩子自立一个块——from clj
这种建块方法严格保证块直径,大小,连通性,不保证块个数,
但是这种类似于SIZE的树上分块方法实际上效果很不错。

鉴于块状树的开发是否会有更大的进展目前暂时不明了,所以这里就此打住。在竞赛中,块状树在暴力的应用应该更显著,毕竟n√\sqrt n的复杂度也确实比较大;

过渡Ⅱ

从块状树上面我们最应该学到的是其树上分块的思想,树上分块与树上分治是解决树上问题的利器。如何树上分块是依据题目特点
、要求所决定的。
所以下一章节便是
——

树上分块

大致上分为4类:

DFS序分块

通过DFS序每N√\sqrt N个点分成一块,好写,非常有利于与
DFS 序有关的题目,且严格保证了块的大小,不保证块直径。

HEIGHT分块

通过Depth每Hmax−−−−√\sqrt {Hmax}分成一块,好写,严格保证块直径,
不保证块大小,保证连通。

SIZE分块

检验父亲所在的size是否小于N√\sqrt N,小于就加入,否则新开
一块,严格保证块直径,大小,连通性,不保证块个数。

王室联邦分块

保证块直径,大小,个数,不保证联通。

归纳

上述4种方法各有优劣,一般说来DFS序、SIZE分块法比较常见,而王室联邦的分块方法则更像真正的高级树上分块方法,详情见下文。至于HEIGHT分块法。。。。我连例题都找不到一道,囧,知道有这样一种概念就好了吧。

BZOJ1086王室联邦

很有趣的题,鉴于普通的树上分块可以被菊花图开卡掉,所以我们可以考虑为其他的块预留一些位置,考虑到当一块size>=b时,就把这些分成一块。深度搜索时仍然是从任意节点开始,维护一个栈,当节点退出递归时便压栈,自上而下的合并至块中。所以当某子树深搜完后栈内元素>=b时便合并成一块。所以其缺点中的不保证联通性就是如果某子树深搜后栈内元素不够b,那么这些元素就会与下一个子树内部的节点合并,直至元素个数>=b。
所以我们每一次递归时维护栈底,对于当前子树,这个栈底即是整个栈的底,栈底下元素不可被修改、弹栈。最后保证了子树内未分分块的节点数<=b,并且每块的大小也不超过2b,将剩余节点暴力塞进最后一块中,那么最大的块就不会超过3b的size的大小。

重点: DFS序与SIZE

尽管有种种什么菊花图、链式图来卡树上分块,(那毕竟是少数无良出题人。。。。。),DFS序分块作为树问题转序列问题的经典方法,是很值得OIer学习的,考虑到维护时间戳,记录节点入度与出度的时间,可以轻松将树压缩与序列之上,剩下的什么分块就可以完美胜任了。

DFS序的运用

给定一颗树, 和每个节点的权值.下面有7个经典的关于dfs序的问题:

  1. 对某个节点X权值加上一个数W, 查询某个子树X里所有点权的和.
    由于X的子树在DFS序中是连续的一段, 只需要维护一个dfs序列,用树状数组实现:单点修改和区间查询.

  2. 对节点X到Y的最短路上所有点权都加一个数W, 查询某个点的权值.
    这个操作等价于
    a. 对X到根节点路径上所有点权加W
    b. 对Y到根节点路径上所有点权加W
    c. 对LCA(x, y)到根节点路径上所有点权值减W
    d. 对LCA(x,y)的父节点 fa(LCA(x, y))到根节点路径上所有权值减W
    于是要进行四次这样从一个点到根节点的区间修改.将问题进一步简化, 进行一个点X到根节点的区间修改, 查询其他一点Y时,只有X在Y的子树内, X对Y的值才有贡献且贡献值为W.当单点更新X时,X实现了对X到根的路径上所有点贡献了W.于是只需要更新四个点(单点更新) ,查询一个点的子树内所有点权的和(区间求和)即可.

  3. 对节点X到Y的最短路上所有点权都加一个数W, 查询某个点子树的权值之和.
    同问题2中的修改方法, 转化为修改某点到根节点的权值加/减W
    当修改某个节点A, 查询另一节点B时
    只有A在B的子树内, Y的值会增加
    W * (dep[A] - dep[B] + 1) => W * (dep [A] + 1) - W * dep[B]
    那么我们处理两个数组就可以实现:
    处理出数组Sum1,每次更新W*(dep[A]+1),和数组Sum2,每次更新W.
    每次查询结果为Sum1(R[B]) – Sum1(L[B]-1) - (Sum2(R[B]) – Sum2(L[B]-1)) * dep [B].

  4. 对某个点X权值加上一个数W, 查询X到Y路径上所有点权之和.
    求X到Y路径上所有的点权之和, 和前面X到Y路径上所有点权加一个数相似
    这个问题转化为
    X到根节点的和 + Y到根节点的和 - LCA(x, y)到根节点的和 - fa(LCA(x,y)) 到根节点的和
    更新某个点x的权值时,只会对它的子树产生影响,对x的子树的每个点到根的距离都加了W.
    那么我们用”刷漆”(差分前缀和),更新一个子树的权值.给L[x]加上W,给R[x]+1减去W,那么sum(1~L[k])就是k到根的路径点权和.

  5. 对节点X的子树所有节点加上一个值W, 查询X到Y的路径上所有点的权值和
    同问题4把路径上求和转化为四个点到根节点的和
    X到根节点的和 + Y到根节点的和 - LCA(x, y)到根节点的和 - parent(LCA(x,y)) 到根节点的
    再用刷漆只更新子树.
    修改一点A, 查询某点B到根节点时, 只有B在A的子树内, A对B才有贡献.
    贡献为W * (dep[B] - dep[A] + 1) => W * (1 - dep[A]) + W * dep[B]
    和第三题一样, 用两个sum1,sum2维护 W *(dep[A] + 1),和W.
    最后答案就是sum2*dep[B]-sum1.

  6. 对子树X里所有节点加上一个值W, 查询某个点的值.
    对DFS序来说, 子树内所有节点加W, 就是一段区间加W.
    所以这个问题就是 区间修改, 单点查询.树状数组+刷漆.
  7. 对子树X里所有节点加上一个值W, 查询某个子树的权值和.
    子树所有节点加W, 就是某段区间加W, 查询某个子树的权值和, 就是查询某段区间的和
    区间修改区间求和,用线段树可以很好解决。

以上引用自here
上面总结得十分详细,不可谓不全。
可以看出dfs序在树上操作中发挥了至关重要的作用,配合上线段树等其他数据结构,可以在基础树上操作内问题上纵横了。
但是dfs序最关键的是其思想:

树型转序列!

无论是线段树还是树状数组(两种烂大街但又无可替代的基础数据结构)都是序列型数据结构,能够转化为序列无疑是解决问题的一种方式。
回想树链剖分,不也是通过dfs将其编号然后植入到其他数据结构中解决吗?

size分块

这种分块方式就更加贴近于本身的树上分块的特点(在树上?):作用于树上。
举个例:Gty的妹子树(注意:没有“超级”)
这道题就是典型的size分块的题:
如果用王室联邦分块的话需要维护每一个块dfs的序最小值和最大值,并且插入操作会破坏原来的性质,所以很GG,不如直接按sizes分块,根节点size< block就加入根,否则新建块。
因为size分块不能保证块的数量,可以被菊花图卡掉,但是本题并没有所以就可以安心的写size,然后每个块维护排序后的值。
对于查询操作,不完整的块(因为是size分块所以只有子树根所在块不完整)暴力,直接把块重建一个图,每个块都整体二分。
修改维护有序,插入也维护有序,当然修改和插入后重新排序也可以.
复杂度 修改插入O(S)O(S) 查询O(S+NSlogS)O(S+\frac{N}{S}log_S)

HEIGHT分块

我只能说无可奉告。。。。

过渡Ⅲ

那么下一章的方向也就很明显了,既然树分块都讲了,那么树分治肯定跑不了了,树的分块给人的感觉就是直接,在块内暴力,块上打标记,具体这么做有什么卵用。。。
花一点点篇幅谈谈吧,鉴于log级别的操作是极其强势的,所以思考分块比树类数据结构强在where?O(1)O(1)查询历史,详细方式见BZOJ2589与BZOJ4763

然后言归正传,边分治、点分治,我来了!!!

树分治

浅谈竞赛中的部分树(块、点、链)【长期更新】相关推荐

  1. html代码id,浅谈html中id和name的区别实例代码

    浅谈html中id和name的区别实例代码 更新时间:2008年07月28日 23:00:55   作者: 这个是form里面的name与id的区别 我们可以通过一段代码来分析一下其中的微妙差别: 在 ...

  2. 视频基础知识:浅谈视频会议中H.264编码标准的技术发展

    浅谈视频会议中H.264编码标准的技术发展 浅谈视频会议中H.264编码标准的技术发展 数字视频技术广泛应用于通信.计算机.广播电视等领域,带来了会议电视.可视电话及数字电视.媒体存储等一系列应用,促 ...

  3. python中怎么调用函数_浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数–封装独立的功能 2.调用函数–享受封装的成果 函数的作用:在开发时 ...

  4. python读取json数据格式问题_浅谈Python中的异常和JSON读写数据的实现

    异常可以防止出现一些不友好的信息返回给用户,有助于提升程序的可用性,在java中通过try ... catch ... finally来处理异常,在Python中通过try ... except .. ...

  5. swift 引用其他类_浅谈swift中闭包修饰符 weak?unowned? 或什么都不用

    浅谈swift中闭包修饰符 weak?unowned? 或什么都不用 平常的开发中,clourse是我们iOSr绕不过去的坎儿. 苹果本身也很重视闭包,像之前的一些老的target-action类型的 ...

  6. 浅谈Vue中的虚拟DOM

    Virtual DOM 是JavaScript按照DOM的结构来创建的虚拟树型结构对象,是对DOM的抽象,比DOM更加轻量型 为啥要使用Virtual DOM 当然是前端优化方面,避免频繁操作DOM, ...

  7. 浅谈Java中的栈和堆

    人们常说堆栈堆栈,堆和栈是内存中两处不一样的地方,什么样的数据存在栈,又是什么样的数据存在堆中? 这里浅谈Java中的栈和堆 首先,将结论写在前面,后面再用例子加以验证. Java的栈中存储以下类型数 ...

  8. python函数定义及调用-浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数�C封装独立的功能 2.调用函数�C享受封装的成果 函数的作用:在开 ...

  9. php css定位到图片上,CSS_浅谈css中图片定位之所有图标放在一张图上,如今做网页为了使网站丰富多 - phpStudy...

    浅谈css中图片定位之所有图标放在一张图上 如今做网页为了使网站丰富多彩,富于表现力,往往需要应用大量的图片/图标.如何处理这些图片,使其尽量不影响网页载入,解析等速度,是一个不大不小的问题.如果你的 ...

最新文章

  1. PHP cron中的批处理(转)
  2. 针对蓝牙4.0 BLE通讯过程的逆向和攻击
  3. docker应用到生产环境的前提
  4. IDC中国AI服务器报告出炉:浪潮继续蝉联第一,份额超过其他玩家总和
  5. iptables命令_理解 Linux 下的 Netfilter/iptables
  6. python发声-python让程序发出声音
  7. Devuan Jessie beta 释出
  8. mongodb占内存过大情况处理
  9. 关于ROS的设置问题
  10. MDF文件查看器(SQL MDF Viewer) 1.0 绿色版
  11. as3 greensock_GreenSock 3 Web动画:了解GSAP的新功能
  12. matlab潮流计算仿真,MATLAB潮流计算仿真
  13. ubuntu 18.04安装绿联千兆USB网卡的AX88179芯片驱动(编译报错解决)
  14. 利用牛顿迭代公式开方
  15. matlab提取多边形区域_如何利用Matlab求任意多边形的面积
  16. [BZOJ1779][Usaco2010 Hol]Cowwar 奶牛战争(最大流)
  17. ES中 Nested 类型的原理和使用
  18. 一车abs线路怎么量_汽车ABS传感器怎么测量好坏?
  19. CPU性能指标了解及字符型服务器登陆方式
  20. 微信小程序dom limit无法渲染更多节点问题

热门文章

  1. Java学习笔记3.7.1 抽象类、接口与多态 - 抽象类
  2. 基于Darknet的YOLOv4目标检测
  3. [CF1536F]Omkar and Akmar
  4. APP活动策划怎么做?活动策划是APP运营最重要的吗?
  5. [链表]链表寻找中间点环形链表反转链表
  6. Mercury 靶机 wp
  7. 统计学中数据类型与几个基本概念
  8. 深度测试depthTest
  9. 档案管理系统软件的档案管理包含哪些内容?
  10. 以AI赋能可持续的用户增长,爱奇艺联合WSDM发起用户留存预测挑战赛