树分治

概述

  • 树分治通过树的唯一连通性质,递归地求解树上路径(主要是路径长度)相关的问题。

  • 树分治主要包括点分治和边分治。我只会点分治。

点分治

  • 点分治通过选取点作为分割来求解树上路径问题。

  • 较具体地说,如果我们选定一个关键点 \(key\) 来分割当前处理的(子)树,那么路径可以分为以下两种:

    • \(key\) 的。

    • 某个子树内部的。

  • 从而我们可以处理前两者,递归求解第三者。

  • 具体来讲,对于过 \(key\) 的路径,处理流程如下:

    • 依次 dfs 每个子树并获取其信息。一般不开桶,因为开不下,而是开栈 \(O(siz)\) 逐个处理。

    • 不断将当前子树的信息和已经处理完的子树的信息合并以计算答案(注意意这代表着点分治的形式只适合路径这种只有两个端的东西(一端已处理,一端在手上),多了就炸了,因为答案没法算了)。

    • 将当前子树的信息扔进已经处理完的子树中。

  • 怎么求 \(key\)

    • 树的所有点都是割点。然而,分治的基本要求一般为“子问题尽量小”。或者,至少,子问题尽量平均(由主定理)。

    • 故考虑使用重心作为分界点。

    • 怎么找重心?先暴力 dfs 一遍求一下每个点的最大子树(注意也包括向父亲方向的那一棵,这就要求一个 \(num\) 来记录当前处理的子树的整体大小),然后 \(mxsub_{rt}>mxsub_{now}\to rt=now\)

    • 到这里我们已经找到了子树的重心,但子树内的 \(siz\) 是错误的(不是以重心为根);而下一层的递归的 \(num\),由本层的 \(siz\) 而来。故再来 dfs 一遍,把 \(siz\) 处理好。

  • 总复杂度为 \(O(n\log)\),证明:启发式分裂。见重剖吧,不想再写了。

P4178 Tree

  • 题意略。

  • 板子,使用 ta 维护半路径(未闭合的)条数即可。

CF150E Freezing with Style

  • 题意:给出一棵带边权树,求一条路径,其边数 \(\in [L,R]\) 且边权中位数最大。特别地,这里中位数在偶数个时取中间较大的。输出任意最优路径的两个端点。

  • 数据范围:\(n\leqslant 10^5,7s\)

  • 首先显然把边权离散化,然后考虑中位数的一个套路:二分答案,转化为 \(01\) 判定问题。鉴于这里我们难以预先知道路径长度,不妨令 \(<mid\) 的为 \(-1\)\(\geqslant mid\) 的为 \(1\),于是和 \(\geqslant 0\) 就说明是合法解。

  • 树上路径问题,考虑用点分治...记录到根的,有 \(k\) 条边的所有路径中的最大权值(当然是 \(01\) 转化后的啦),于是合并的时候只要查询...记当前路径长度为 \(ln\),权值为 \(vn\),查询 \([\max_{i=L-ln}^{R-ln} v+vn\geqslant 0]\) 即可。

  • 我们看到随着二分的 \(mid\) 变化,需要多次点分治。考虑使用点分树...诶诶诶?好像点分树在这里完全没有作用啊?边权都变了?

  • 那就暴力重新分治,这首先是 \(O(n\log^2)\) 的...然而区间查询最大值...线段树维护的话,\(O(n\log^3)\)。纸面 \(4.9\times 10^8\)...乍一看是不卡的,但线段树和点分治都是大常数 qaq。

  • 所以...这位先生,能否占用你一点时间,我想向你介绍一下我们伟大的神器——单调队列按秩合并!

  • 首先我们考虑这个问题是否满足优先队列的形式:似乎是可以的。

  • 如果我们将当前子树的结果排序(事实上,改用 bfs 实现就足以达到这个效果),那么随着枚举当前子树的结果,\(L-ln\)\(R-ln\) 都是单调不降的。

  • 故只要在扫的时候,不断地移动重心这边结果数组上的 \(hd,tl\),使得 \(hd\geqslant L-ln,tl\to R-ln\) 即可,这里 \(\to\) 表示尽量趋近,即不超过的前提下最靠近。

  • 不妨记 \(num\) 为当前分治的子树点数。这里重心这边的结果数组显然不能是 \(1\sim num\) 的一个密铺,而应该本身就是一个单调队列;特别的是,这个单调队列的尾端竞争是有限的——不能因为尾端竞争使得一个可能的,完整的转移来源区间中一个数都没有。

  • 然后考虑怎么合并:暴力合并单调队列就是从两者的队头不断取元素,尾端插入到一个新的单调队列中,显然是 \(O(siz+siz)\) 的;但我们可以将较小的单调队列中的元素插入到较大的单调队列中的对应位置,照样进行尾端竞争操作。

  • 乍一看每次都要找位置是 \(O(\min(siz)\log max(siz))\),实则不然,发现插入的位置也是单调递增的,故只要双指针...单指针?即可。

  • 分析一下合并复杂度:乍一看是类似重剖的启发式合并,还是带 \(\log\),然而仔细一想...仔细一想这h=还是 \(O(siz+siz)\) 啊摔!双指针会扫完的啊,拜托...

  • 看来这条路走不通——事实上转移求值部分似乎也是 \(O(siz+siz)\),这还不如线段树呢,毕竟我们知道这个的本质是 \(O(n^2)\) 的树上背包...不对,背包是乘,这个是加,设有 \(k\) 个儿子,第 \(i\) 个转移上来的儿子会被后面影响 \(k-i\) 次。

  • 嗯?这好像暗示我们把小儿子——我是指深度较小的儿子——放到前面去。嘛,毕竟复杂度要么是优化下来,要么是分析下来,我们分析一手试试:

  • 若总有 \(siz\leqslant sizn\)...哦这是长剖。可以认为是合并上来的儿子带了 \(2\) 倍常数,于是忽略合并复杂度。芜湖!消了一只 \(\log\)!只要在第一次点分治的时候将各方向儿子排序就好啦,这一排序也是 \(O(n\log^2)\)

  • 注意到这样一来维护那个密铺就可以,没必要将其简化到一个单调队列。实现也容易了不少呢。另外最好还是建出点分树,我是指不要每次都重新找重心,以减小常数——这是古早 CF 题,在更新服务器后...强制运行时间翻倍。

点分树

  • 即可持久化点分治,将点分治的树形结构(重心之间的父子关系)拉下来,于是树高为 \(O(\log)\),这相当于随机数据的树高可以跑很多暴力。

  • 有性质:任意两点在点分树上的 \(lca\),在原树意义上一定在两点之间的路径上。

    • 证明:否则两者同子树,应进一步分治。

    • 这使得点分树上的 \(lca\) 有时也能充当 \(lca\) 来计算一些东西,譬如开店这道题。

P6329 【模板】点分树 | 震波

  • 题意略。

  • 考虑容斥,即在每个重心处用树状数组存每个距离的点的价值和,又另外存在父重心看来我这棵子树的每个距离的点数,然后从节点向根暴力回跳容斥即可。

  • 修改也容易,思路基本一样,略。

P3241 [HNOI2015] 开店

  • 题意略。

  • 考虑向两个方向弱化这个问题。

    • 如果没有 L,R 的限制:经典真换根,强制在线的问题可以用预先算出所有答案来解决,或者假换根,\(O(n)\)

    • 如果 u 是固定的:当然有简单的办法比如求深度直接加上去,但这是不可推广的。考虑线段树合并,用在线的那种,\(O(n\log n)\)

  • 考虑结合到一起。不妨以假换根为例,那么多出来的部分就是 \(dw_{fa}-delta(now)+up_{fa}\)

    • 这里的问题是可减的。故上式的正确性显然。

    • 然而加减的复杂度都炸飞了。退化成 \(O(n^2\log)\)。减的话,对于直接连边会炸。加一定会炸,把树卡满就好了。

    • 真换根显然也有类似的问题,因为加减是一样的。

    • 好吧...至少我们知道了线合碰换根(不论真假)就死。

  • 把换根毙掉,考虑别的能求距离和的方法。有一个经典的基于树上差分的式子:\(\sum\limits_{i=1}^n dis(i,x)=n\times dep_x+\sum\limits_{i=1}^n dep_i-2\sum\limits_{i=1}^n dep_{lca(i,x)}\)

  • 显然主要的问题在于求第三项。将每个点到根的路径覆盖一下,于是 \(\sum\limits_{i=1}^n dep_{lca(i,x)}\) 可以转化为 \(x\) 到根的路径上每条边的覆盖次数乘以对应的边权(这其实是对路径做反演,换到边上统计贡献)。

  • 树剖+主席树维护之,复杂度 \(O(n\log^2)\),但需要主席树支持树剖式修改,还蛮恶心的。

  • 另一个方向的思路则是点分树,因为点分树的树高为 \(O(\log)\),这代表着也许我们可以暴力跳 \(lca\)

    • 这里原树上 \(lca\) 的主要作用是转化为树上差分问题,但我们能不能不差分?即,把式子化成 \(\sum\limits_{i=1}^n dis(lca(i,x),x)+dis(lca(i,x),i)\)

    • 显然这样转化后我们的 \(lca\) 已经没有什么特殊的性质了,将其换成点分树上的 \(lca\) 也行。

    • 考虑点分树上容斥,自点分树的根暴力走到 \(x\),其间用点分树上父节点减去子节点对它的贡献(这一容斥不好直接做,只能记录下贡献量),然后加上算出的父节点到 \(x\) 的路径长度乘以点数即可。该路径长度可以暴力求,就是 \(O(n\log)\) 的,利用同样的手法可以避免开线段树,但不可避免地要 vector 上二分,复杂度降不下来。

  • 总复杂度 \(O(n\log^2)\),但常数不知道了。也很恶心。

posted @ 2023-01-15 10:12  未欣  阅读(42)  评论(0编辑  收藏  举报