ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Dsu on tree 和 点分治

2021-10-15 19:02:20  阅读:156  来源: 互联网

标签:Dsu 合并 复杂度 分治 tree times


写的有点乱,意识流 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_{1\sim k}\)。合并和上面是一样的。然后对于每个 \(y_i\) 和 \(r\) 都分别递归,然后将其依次并入 \(x\)​ 子树的信息中。途中可以直接完成 \(x, y_{1\sim p}\) 答案的计算——仔细观察点分治的递归方式可以发现除了一些与 \(r\)​ 之间相连的部分不被当前连通块包含,其他都是完整的,可以准确得到答案。那么对于 \(r\) 我们保留合并得到的信息,接应之后可能的合并。

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

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

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

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

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

点分治与静态计算

考虑这样一个问题:

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

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

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

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

那么如果强行转化为动态又将如何?设我们维护出 \(V\subseteq 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\to r\) 的多项式 \(f\) 怎么算。比较无脑的有分治 FFT,这样总复杂度 \(O(n\log ^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_1R\le -(s_2-c_2R) \\ s_1-c_1L\ge -(s_2-c_2L) \\ \]

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

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

标签:Dsu,合并,复杂度,分治,tree,times
来源: https://www.cnblogs.com/-Wallace-/p/dsu-on-tree-and-vdac.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有