zoukankan      html  css  js  c++  java
  • Dsu on tree 和 点分治

    写的有点乱,意识流 blog /yun

    感谢 @Mr_Spade 对我的帮助。

    从 Dsu on tree 到点分治

    Dsu on Tree 的主要思想是轻重链剖分,使每个点作为根时,都继承重儿子的信息并快速加入根的影响,并对每个轻儿子暴力向重儿子合并。由于每个点被合并 (O(log n)) 次,复杂度是正确的。

    这种问题也可以用点分治实现。对于无根树上的问题,对点分得到的若干部分分别遍历,然后用和上面同样的数据结构维护查询和插入操作。

    而对于有根树中“对每个子树求解”的问题则相对复杂。考虑有根点分治,记分治中心为 (x)(x) 下方的子树为 (x_1, x_2,cdots, x_k)(x) 上方的祖先为 (y_1,y_2,cdots, y_p)​,整个连通块的根为 (r)(不包含在 (y)​ 中)。考虑先计算子树 (x) 下方的答案,递归 (x_{1sim k})。合并和上面是一样的。然后对于每个 (y_i)(r) 都分别递归,然后将其依次并入 (x)​ 子树的信息中。途中可以直接完成 (x, y_{1sim p}) 答案的计算——仔细观察点分治的递归方式可以发现除了一些与 (r)​ 之间相连的部分不被当前连通块包含,其他都是完整的,可以准确得到答案。那么对于 (r) 我们保留合并得到的信息,接应之后可能的合并。

    可以看到基本上 Dsu on tree 可以做的点分治也可以做,但实际上本质是差不多的。考虑你想要把 (n)​ 个一次多项式乘起来,你可以套用哈夫曼树的方法,维护一个优先队列,每次取两个小的相乘然后放回堆中;或者使用分治,先将 (f_l imes f_{l+1} imes cdots imes f_{mid})​ 以及 (f_{mid+1} imes f_{mid+2} imes cdots imes f_r)​ 算好,再计算 ([l,r])​​ 全部相乘的结果。这分别对应着 Dsu on tree 和点分治,两者都是将 (n)​​ 个点合并,只是保证复杂度的方式不同。

    一般而言做有根这种题都优选 Dsu on tree,因为效果几乎一样并且常数和代码都比较优秀。上面这段文字只是说明了:

    Dsu on tree 能解决的问题为点分治的真子集

    听起来是暴论但实际上几乎正确。考虑 Dsu on tree 的操作条件是:可以快速对重儿子加上根的影响;并且支持将轻儿子的内容高效动态插入。

    然而一旦满足这些条件,点分治也可以操作。但你发现在这方面的问题点分治的做法都比较繁琐。这是因为点分治的擅长之处完全不在动态插入类问题。

    点分治与静态计算

    考虑这样一个问题:

    给定两棵树 (S,T),带边权且 不一定非负。求 (maxlimits_{u,v} extit{dist}_S(u,v)+ extit{dist}_T(u,v))。​

    由于边权非负,合并点集并由原来两对最远点得到新的最远点的结论已不适用了。下面需要用到一个合并优化的技巧:

    合并优化:对于当前分治中心连接的若干部分 (x_1, x_2, cdots, x_k)​​​,每次选取大小最小的合并并放回作为一个新的部分。这样可以证明效率能达到边分治一样的复杂度,具体证法可见 zzy 论文(?)。这个 trick 的功效在于,可以想边分治一样只用考虑两个不同的部分而不是多个,可以为解题扫清一些障碍。

    对于两个在树 (S) 上的点集 (V_1,V_2),我们建出 (V_1cup V_2)(T)​ 上的虚树。首先求出 (S) 中每个点到分治中心的距离作为在 (T) 虚树 (T') 的点权。那么只要得到 (T')​ 上距离加上两边点权的最大值即可。具体实现类似于树型 dp。可以发现这实际上实现了一个静态问题的求解过程:建虚树然后 dp。

    那么如果强行转化为动态又将如何?设我们维护出 (Vsubseteq S),然后一个新的点 (x)​ 需要被计算贡献。那么唯一的办法是求出 (T)(V) 中的点的点权加上与 (x) 距离的最大值,这本身就是极其复杂的问题。因此 Dsu on tree 直接怎么做是不太行的。

    可以发现,点分治擅长统计可以静态计算的信息。动态维护往往会比前者更加困难,或者是被转化为一个“更强的问题”。那也不奇怪为什么 Dsu on tree 的适用范围更小了。

    快速添加根的影响?

    这是经常忽略的一个要点。考虑这样一个题:

    (n) 个点的树,每个点 (i) 有一次多项式 (x-a_i)。求每个点到根结点 (1) 路径上多项式的乘积,输出它们求和后的结果。

    这题有一个基于长链剖分链分治的做法,这里略去。

    暴力做法是对子结点求和,并乘上自己,复杂度平方。考虑有根点分治(一下记号和上文一致):对于分治中心 (x)​​,我们先递归计算 (x_1, x_2, cdots, x_k)​,对它们的多项式求和记作 (g),这里复杂度不超过 (x) 子树大小。然后递归 (x) 父亲方向的连通块,那么这时除了子树 (x) 都完成对 (r)​​ 贡献的统计了。

    唯一的问题是 (x o r) 的多项式 (f) 怎么算。比较无脑的有分治 FFT,这样总复杂度 (O(nlog ^3 n)),可以利用之前递归子问题结果的技巧将其去掉一个 (log)​,这个暂且不谈。

    尝试使用 Dsu on tree?对于重儿子求出答案,乘上根直接作为根的答案,然后对所有轻儿子合并?不幸的是“乘上根”这个操作复杂度就和重子树相关了——多项式乘法没法像数据结构那样快速添加影响。实际上稍微冷静一下发现这玩意就是暴力。

    更多的时候,这一点可能表现为”难以全局打 tag“之类的情况。

    本质原因……

    具体的,dsu on tree 的短板归纳为两点:

    • 在静态计算简单但维护动态添加困难的时候,无法快速合并轻子树;
    • 无法高效加入根的影响;

    可以发现上面两点说明了一件事:dsu on tree 高度依赖动态添加的高效方法并且能做高效的全局操作(如果需要的话)。

    为什么会这样?注意到点分治之所以可以随意遍历整个连通块,是因为重心分解的分治结构做了保障。而 Dsu on tree 却必须靠规避重子树来保证复杂度。

    例子

    给定一颗树,带边权,求路径上边权平均值 (in [L,R]) 的路径数。

    首先记 ((c,s)) 为一条边数为 (c) 总权值为 (s) 的路径。那么对于两条路径 ((c_1,s_1),(c_2,s_2)),由合法条件可以推出两个不等式:

    [s_1-c_1Rle -(s_2-c_2R) \ s_1-c_1Lge -(s_2-c_2L) \ ]

    这个式子一边都只与一条路径有关,抽象成两维坐标就是数点问题了。考虑点分治做法,如果是采用合并优化并静态统计的方法其实很容易,只是简单的扫描线和数据结构。这样总复杂度为 (O(nlog^2 n))

    而另一种动态添加的思路就比较困难——天然的强制在线使得问题棘手了很多,我们似乎避不开高级数据结构,甚至可能还有更劣的复杂度。

    本文来自博客园,作者:-Wallace-,转载请注明原文链接:https://www.cnblogs.com/-Wallace-/p/dsu-on-tree-and-vdac.html

  • 相关阅读:
    Python基础(2)
    Python基础(3)
    Python基础(1)
    [日本语]单词1
    【.Net】 C#参数数组与函数重载
    pyenv
    Pip批量安装/卸载包
    Xcode中模拟器慢
    iPhone的设备名转换
    在python命令行执行sudo命令
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/dsu-on-tree-and-vdac.html
Copyright © 2011-2022 走看看