起因:cjx 暑假集训的时候出了道题,老师说可以点分治。但是我最初的想法其实是换根处理,但怎么想发现都行不通,因为要同时维护 DFS 序和权值。于是就没想了。后来 10.5 和 xyh 进行长达 30s 的讨论 导游的工作 那题,说了我这个想法,xyh 觉得有道理,对要求解的问题具体化,于是我才想出了分块这一数据结构。并在 xyh 的指导下进行卡常。但其实这个方法已经被发明过了。Tree 那道题的题解里有提到过。
这个方法的复杂度是 \(O(n\times P)\),其中 \(P\) 为一个多项式,代表你使用的数据结构的复杂度。也就是说,如果你的 \(P\) 可以足够好,这个方法甚至可以是点分治的平替。
例题 1:BNDSOJ 1502 导游的工作
Method 1:暴力换根
我们钦定 \(1\) 到 \(n\) 中的每一个点为根。对于每一个根 \(u\) 算出整个树上每个点的深度,判断有多少个点的深度小于等于 \(c\) 即可。这是显然的,时间复杂度为 \(O(n^2)\)。
Method 2:分块优化换根
基本的思想还是 暴力统计。我们只是在用数据结构强行优化这个暴力。
考虑 树上换根动态规划 的思想。统计每个点的答案贡献。前提是你对换根法有了较为深刻的理解。但我们知道,如果把根从 \(u\) 换到儿子 \(v\),每个点的深度都会随之改变。而且这个改变关于原来点的编号不连续。但好消息是,我们在这里并不关注原先的点的编号是什么。
那我们怎样快速修改这个“不连续”的区间呢?如果你知道 树链剖分。那你一定知道它的下位替代—— DFS 序线段树 吧。其实就是根据 子树的 DFS 序连续 这一特性,把不连续的编号转化为连续的 DFS 序进行修改。在这一题里也同样是这么使用的。
但是这题我们并不能使用线段树。因为我们要的操作是这两种:
- 区间加减 \(w\)
- 全局询问小于等于 \(c\) 的 depth 的个数
其实第二个的全局我们可以加强成区间。你会发现这个东西基本等价于 P2801 教主的魔法。于是我们可以使用 分块 维护。
于是时间复杂度为 \(O(n \times P) = O(n\log n \sqrt{n} )\)
例题 2:P3806 【模板】点分治 1
这个因为时限卡的过死于是不能满分,虽然说暴力也可以那道 60 pts,但是我想说这个换根的方法是远优于暴力的。
这个题其实和上面那个题没有本质区别。还是换根,然后询问全局有没有等于 \(c\) 的数,依然可以块内二分解决。
最主要的不同就是把询问离线下来,每次换根的时候都处理一次 \(Q\) 个询问即可。
例题 3:P4178 Tree
这个就纯纯与导游的工作那题没有任何区别,所以我认为导游的工作那题也是紫。
就是把求最大变成全局统计小于等于 \(k\) 的点对了啊。
注意如果换根统计出来的答案是 \(ans\),则真正的答案应该为 \(\frac{ans-n}{2}\)。
例题 4:P2634 [国家集训队] 聪聪可可
稍微变一下形,这题的复杂度是 \(O(n\sqrt{n})\),因为没有块内排序和块内二分。
就是询问树上边权和为 \(3\) 的倍数的路径数。考虑每一块维护三个值。分别代表深度模 \(3\) 余 \(0,1,2\) 的个数。然后每次加 \(w\) 或减 \(w\),其实就相当于加 \(0/1/2\)。加 \(0\) 不用管,加 \(1/2\) 就相当于模 \(3\) 余 \(0,1,2\) 的个数互相交换。
注意散块的处理要尤为注意,就是因为散块,我们还要维护一个区间加再模 \(3\) 的 tag。修改的时候散块也是最麻烦的。
主要难点都是在分块。换根本身没啥难的。