树分治相关
一种特别的分治思想,但难点不在于点分治思想本身。
有板子,但是板子跟题目重点几乎无关。
点分治 淀粉质
用途:
用于处理树上多对点询问或寻找有条件最远(最近)点对。主要是处理多对点对。
做法:
我们先选择一个节点作为根节点 \(rt\) ,所有完全位于其子树中的路径可以分为两种,一种是经过当前根节点的路径,一种是不经过当前根节点的路径。前一种我们在这次分治中得到,后面一种在之后的分治中计算,就转化为了第一种路径。
考虑枚举分治中心 \(rt\) 的子节点 \(u\) ,对于经过 \(rt\) 的路径上的所有点 \(v\) ,\(u\) 都要与其计算一次贡献,可以得出 \(v\)的点集 是 \(rt\) 不包含 \(u\) 的子树的子树中所有点。
一般我们考虑容斥或不计算 \(u\) 子树内结点与 \(u\) 产生的额外(或者错误)贡献。
选取分治中心为每次分治后的树的重心,总处理结点数量为\(O(nlogn)\)
树的重心用一个简单树型dp求。
处理方法与难点:
跟分块一样,分治之后处理是一大问题。常见的处理方式有两种:
- 套用数据结构,对于每个分治中心维护一个动态的数据结构(如动态开点线段树,平衡树),直接区间查询修改处理子树内贡献
- 利用排序+二分或双指针,将数据有序化并尽快找到所需的区间端点。
两种方法各有优缺。
对于套用数据结构,会增加 \(logn\) 级别的常数,此外一般需要支持动态开点保证不爆空间。
对于双指针,要注意扫描一次序列时间复杂度是 \(O(L)\) 的,\(L\) 是序列长度。而二分是 \(O(logL)\) 的,如果不是在一次序列中求多对符合要求的区间,尽量使用二分。
离散化和桶排序是点分治中会常用的思想,没思路的时候可以往这两个方面想。如果值域很大桶装不下,可以考虑将权值存出来然后排序。权值相同的元素一定相邻。
难点在于思考出合适的处理方法。实在想不到就乱搞吧
边分治
思路:
与上面的点分治类似,我们选取一条边,把树尽量均匀地分成两部分(使边连接的两个子树的 \(size\) 尽量接近)。然后递归处理左右子树,统计信息。
如果是个菊花图,每次断边只会断掉一个结点,所以会被卡成 \(O(n^2)\) ,而二叉树性质很优秀啊(),这时候就需要多叉转二叉(也就是三度化)。
三度化:
三度化方式有两种。写法简单的一种是:
把所有儿子依次加一个点串起来
就算把所有点都加一个点串起来,我们也最多增加了 \(n\) 个点, \(n\) 条边,所有复杂度还是同阶的。
新增的点和边要注意他们的值,一般将点权和边权设置为 \(0\) ,也有例外,所以还是根据题目需要来。
可以模拟一下发现,即使分治中心是虚边,也不会存在统计重复的点对。
小细节是因为我们需要知道分治中心边的两端点,所以需要记录是第几条边。于是乎需要用到成对变换来存边。
code:
void mkmap(int x,int fa){//三度化(多叉转二叉)
int tnd=0;
for(auto it:og[x]){
if(it.y==fa)continue;
if(!tnd){
link(x,it.y,it.w);
link(it.y,x,it.w);
tnd=x;//该连接的结点
}
else{
++nn;
//赋值 !!!
link(tnd,nn,0);link(nn,tnd,0);//赋值!!!
link(it.y,tnd,it.w);link(tnd,it.y,it.w);
tnd=nn;
}
mkmap(it.y,x);
}
}
处理方法/优点:
边分治的处理方法比点分治容易,有时候还能省下为了容斥才使用到的数据结构使得复杂度降低码量。
边分治一般不用考虑容斥,一个点在左端点的子树内,那么要统计贡献的点一定都在右端点的子树内。反之同理。所以可以左右子树分开存互不影响。
边分治将每次的分治联通块中的点恰好分成了两部分,这就省去了像点分治那样单独处理以分治重心为路径端点的答案这一过程。
适用/局限:
几乎所有点分治的题边分都能做。
局限在于虚点虚边必须不影响答案
点分树(动态点分治):
定义:
点分树是通过更改原树形态使得层数变为\(logn\)级别。
点分治的过程就是构造点分树的过程。将上一层的分治中心 \(u\) 与他的下一层分治中心 \(v\) 连边,重构出一棵虚树。(实际上一般 \(fa_v=u\) 就行)
用途:
处理多次询问和带修的点分治问题。并且不会修改操作改变原树的形态(即不会改变每次重心的选取。)
在点分数上向上暴力跳链复杂度是不大于 \(O(logn)\) 的(因为层数不大于 \(O(logn)\) )。
对于任意一个 \(y\),首先在虚树上找到它与 \(x\) 的 \(lca\) 该点必定在原树的 \(x,y\) 路径上。
- 看不懂的证明:\(lca\) :(或者说囊括连通块同时包含 \(x,y\) 的所有虚树节点中深度最深的那一个),易知在以此点为重心划分子连通块时 \(x,y\)会首次被分割开来
\(x\) 与某个结点在虚树上的 \(lca\) 一定是 \(x\) 的祖先,所以这些点都在 \(x\) 的祖先 除开 \(x\) 的子树的子树上(类似于点分治的味道),因为 \(x\) 的祖先在 \(x\)子树内的结点与\(x\) 的 \(lca\) 不是这个祖先而是 \(x\) 到这个祖先的链上(不含这个祖先)的一个结点。
实现:
与点分治板子部分相同。只是多一个建树过程。
处理方法:
对于一个点,我们要统计或修改其对所有虚树上祖先的贡献。这个过程可以暴力向上跳完成。
通常我们需要统计虚树上的结点到其虚树上子节点的实树上的信息。实树上的信息与虚树上无关,例如处理虚树上的点到父亲的距离需要在实树上树链剖分求链长。
注意事项:
实树上的信息与虚树无关,时刻警惕你需要哪棵树的信息。
关于你自己的注意事项:
看看你统计子树大小的时候有没有递归处理!!!!
标签:结点,处理,分治,tnd,link,相关,树上 From: https://www.cnblogs.com/cmachsocket/p/17997232