Loading docs/misc/dsu-on-tree.md +21 −21 Original line number Diff line number Diff line Loading @@ -39,21 +39,21 @@ void merge(int x,int y) 既然支持离线,考虑预处理后 $O(1)$ 输出答案。 直接暴力预处理的时间复杂度为$O(n^2)$,即对每一个子节点进行一次遍历,每次遍历的复杂度显然与$n$同阶,有$n$个节点,故复杂度为$O(n^2)$ 直接暴力预处理的时间复杂度为 $O(n^2)$,即对每一个子节点进行一次遍历,每次遍历的复杂度显然与 $n$ 同阶,有 $n$ 个节点,故复杂度为 $O(n^2)$。 可以发现,每个节点的答案是其子树的叠加,考虑利用这个性质处理问题 可以发现,每个节点的答案是其子树的叠加,考虑利用这个性质处理问题。 我们可以先预处理出每个节点子树的size和它的重儿子,重儿子同树链剖分一样,是拥有节点最多子树的儿子,这个过程显然可以O(n)完成 我们可以先预处理出每个节点子树的 $size$ 和它的重儿子,重儿子同树链剖分一样,是拥有节点最多子树的儿子,这个过程显然可以 $O(n)$ 完成 我们用 check[i] 表示颜色$i$有没有出现过,ans[i] 表示他的颜色个数 遍历一个节点,我们按以下的步骤进行遍历: - 先遍历其非重儿子,获取它的ans,但**不保留遍历后它的check** - 先遍历其非重儿子,获取它的 ans,但**不保留遍历后它的 check**; - 遍历它的重儿子,**保留它的check** - 遍历它的重儿子,**保留它的 check**; - 再次遍历其非重儿子及其父亲,用重儿子的check对遍历到的节点进行计算,获取整棵子树的ans - 再次遍历其非重儿子及其父亲,用重儿子的 check 对遍历到的节点进行计算,获取整棵子树的 ans;  Loading @@ -65,18 +65,18 @@ _上图是一个例子_ 为什么不合并第一步和第三步呢?因为 check 数组不能重复使用,否则空间会太大,需要在 $O(n)$ 的空间内完成。 显然若一个节点u被遍历了x次,则其重儿子会被遍历x次,轻儿子(如果有的话)会被遍历x*2次。 显然若一个节点 $u$ 被遍历了 $x$ 次,则其重儿子会被遍历 $x$ 次,轻儿子(如果有的话)会被遍历 $2x$ 次。 注意除了重儿子,每次遍历完 $check$ 要清零。 ### 复杂度 (对于不关心复杂度证明的,可以跳过不看) 我们像树链剖分一样定义重边和轻边(连向重儿子的为重边,其余为轻边)关于重儿子和重边的定义,可以见下图,对于一棵有n个节点的树: 我们像树链剖分一样定义重边和轻边(连向重儿子的为重边,其余为轻边)关于重儿子和重边的定义,可以见下图,对于一棵有 $n$ 个节点的树: 根节点到树上任意节点的轻边数不超过logn条。我们设根到该节点有x条轻边该节点的子树大小为y,显然轻边连接的子节点的子树大小小于父亲的一半(若大于一半就不是轻边了),则$y<n/2^x$,显然$n>2^x$,所以$x<logn$。 根节点到树上任意节点的轻边数不超过 $\log n$ 条。我们设根到该节点有x条轻边该节点的子树大小为 $y$,显然轻边连接的子节点的子树大小小于父亲的一半(若大于一半就不是轻边了),则 $y<n/2^x$,显然 $n>2^x$,所以 $x<\log n$。 又因为如果一个节点是其父亲的重儿子,则他的子树必定在他的兄弟之中最多,所以任意节点到根的路径上所有重边连接的父节点在计算答案是必定不会遍历到这个节点,所以一个节点的被遍历的次数等于他到根节点路径上的轻边树+1(之所以要+1是因为他本身要被遍历到),所以一个节点的被遍历次数=logn+1,总时间复杂度则为$O(n(logn+1))=O(nlogn)$输出答案花费$O(m)$. 又因为如果一个节点是其父亲的重儿子,则他的子树必定在他的兄弟之中最多,所以任意节点到根的路径上所有重边连接的父节点在计算答案是必定不会遍历到这个节点,所以一个节点的被遍历的次数等于他到根节点路径上的轻边树 $+1$(之所以要 $+1$ 是因为他本身要被遍历到),所以一个节点的被遍历次数 $=\log n+1$,总时间复杂度则为 $O(n(\log n+1))=O(n\log n)$,输出答案花费 $O(m)$.  Loading Loading @@ -128,15 +128,15 @@ int dfs2(int u,int fa,bool keep,bool isson){ ### 运用 1.某些出题人设置的正解是 dsu on tree 的题 如[CF741D](http://codeforces.com/problemset/problem/741/D),给一棵树,每个节点的权值是'a'到'v'的字母,每次询问要求在一个子树找一条路径,使该路径包含的字符排序后成为回文串。 如 [CF741D](http://codeforces.com/problemset/problem/741/D)。给一棵树,每个节点的权值是'a'到'v'的字母,每次询问要求在一个子树找一条路径,使该路径包含的字符排序后成为回文串。 因为是排列后成为回文串,所以一个字符出现了两次相当于没出现,也就是说,这条路径满足**最多有一个字符出现奇数次** 因为是排列后成为回文串,所以一个字符出现了两次相当于没出现,也就是说,这条路径满足**最多有一个字符出现奇数次**。 正常做法是对每一个节点dfs,每到一个节点就强行枚举所有字母找到和他异或后结果为1的个数<1的路径,再去最长值,这样$O(n^2logn)$的,可以用dsu on tree优化到$O(nlog^2n)$.关于具体做法,可以参考下面的扩展阅读 正常做法是对每一个节点 dfs,每到一个节点就强行枚举所有字母找到和他异或后结果为1的个数<1的路径,再去最长值,这样 $O(n^2\log n)$ 的,可以用 dsu on tree 优化到 $O(n\log^2n)$。关于具体做法,可以参考下面的扩展阅读 2.可以用dsu乱搞~~吊打std~~水分的题 可以水一些树套树的部分分(没有修改操作),还可以把树上莫队的$O(n\sqrt{m})$吊着打 可以水一些树套树的部分分(没有修改操作),还可以把树上莫队的 $O(n\sqrt{m})$ 吊着打 ### 练习题 Loading Loading
docs/misc/dsu-on-tree.md +21 −21 Original line number Diff line number Diff line Loading @@ -39,21 +39,21 @@ void merge(int x,int y) 既然支持离线,考虑预处理后 $O(1)$ 输出答案。 直接暴力预处理的时间复杂度为$O(n^2)$,即对每一个子节点进行一次遍历,每次遍历的复杂度显然与$n$同阶,有$n$个节点,故复杂度为$O(n^2)$ 直接暴力预处理的时间复杂度为 $O(n^2)$,即对每一个子节点进行一次遍历,每次遍历的复杂度显然与 $n$ 同阶,有 $n$ 个节点,故复杂度为 $O(n^2)$。 可以发现,每个节点的答案是其子树的叠加,考虑利用这个性质处理问题 可以发现,每个节点的答案是其子树的叠加,考虑利用这个性质处理问题。 我们可以先预处理出每个节点子树的size和它的重儿子,重儿子同树链剖分一样,是拥有节点最多子树的儿子,这个过程显然可以O(n)完成 我们可以先预处理出每个节点子树的 $size$ 和它的重儿子,重儿子同树链剖分一样,是拥有节点最多子树的儿子,这个过程显然可以 $O(n)$ 完成 我们用 check[i] 表示颜色$i$有没有出现过,ans[i] 表示他的颜色个数 遍历一个节点,我们按以下的步骤进行遍历: - 先遍历其非重儿子,获取它的ans,但**不保留遍历后它的check** - 先遍历其非重儿子,获取它的 ans,但**不保留遍历后它的 check**; - 遍历它的重儿子,**保留它的check** - 遍历它的重儿子,**保留它的 check**; - 再次遍历其非重儿子及其父亲,用重儿子的check对遍历到的节点进行计算,获取整棵子树的ans - 再次遍历其非重儿子及其父亲,用重儿子的 check 对遍历到的节点进行计算,获取整棵子树的 ans;  Loading @@ -65,18 +65,18 @@ _上图是一个例子_ 为什么不合并第一步和第三步呢?因为 check 数组不能重复使用,否则空间会太大,需要在 $O(n)$ 的空间内完成。 显然若一个节点u被遍历了x次,则其重儿子会被遍历x次,轻儿子(如果有的话)会被遍历x*2次。 显然若一个节点 $u$ 被遍历了 $x$ 次,则其重儿子会被遍历 $x$ 次,轻儿子(如果有的话)会被遍历 $2x$ 次。 注意除了重儿子,每次遍历完 $check$ 要清零。 ### 复杂度 (对于不关心复杂度证明的,可以跳过不看) 我们像树链剖分一样定义重边和轻边(连向重儿子的为重边,其余为轻边)关于重儿子和重边的定义,可以见下图,对于一棵有n个节点的树: 我们像树链剖分一样定义重边和轻边(连向重儿子的为重边,其余为轻边)关于重儿子和重边的定义,可以见下图,对于一棵有 $n$ 个节点的树: 根节点到树上任意节点的轻边数不超过logn条。我们设根到该节点有x条轻边该节点的子树大小为y,显然轻边连接的子节点的子树大小小于父亲的一半(若大于一半就不是轻边了),则$y<n/2^x$,显然$n>2^x$,所以$x<logn$。 根节点到树上任意节点的轻边数不超过 $\log n$ 条。我们设根到该节点有x条轻边该节点的子树大小为 $y$,显然轻边连接的子节点的子树大小小于父亲的一半(若大于一半就不是轻边了),则 $y<n/2^x$,显然 $n>2^x$,所以 $x<\log n$。 又因为如果一个节点是其父亲的重儿子,则他的子树必定在他的兄弟之中最多,所以任意节点到根的路径上所有重边连接的父节点在计算答案是必定不会遍历到这个节点,所以一个节点的被遍历的次数等于他到根节点路径上的轻边树+1(之所以要+1是因为他本身要被遍历到),所以一个节点的被遍历次数=logn+1,总时间复杂度则为$O(n(logn+1))=O(nlogn)$输出答案花费$O(m)$. 又因为如果一个节点是其父亲的重儿子,则他的子树必定在他的兄弟之中最多,所以任意节点到根的路径上所有重边连接的父节点在计算答案是必定不会遍历到这个节点,所以一个节点的被遍历的次数等于他到根节点路径上的轻边树 $+1$(之所以要 $+1$ 是因为他本身要被遍历到),所以一个节点的被遍历次数 $=\log n+1$,总时间复杂度则为 $O(n(\log n+1))=O(n\log n)$,输出答案花费 $O(m)$.  Loading Loading @@ -128,15 +128,15 @@ int dfs2(int u,int fa,bool keep,bool isson){ ### 运用 1.某些出题人设置的正解是 dsu on tree 的题 如[CF741D](http://codeforces.com/problemset/problem/741/D),给一棵树,每个节点的权值是'a'到'v'的字母,每次询问要求在一个子树找一条路径,使该路径包含的字符排序后成为回文串。 如 [CF741D](http://codeforces.com/problemset/problem/741/D)。给一棵树,每个节点的权值是'a'到'v'的字母,每次询问要求在一个子树找一条路径,使该路径包含的字符排序后成为回文串。 因为是排列后成为回文串,所以一个字符出现了两次相当于没出现,也就是说,这条路径满足**最多有一个字符出现奇数次** 因为是排列后成为回文串,所以一个字符出现了两次相当于没出现,也就是说,这条路径满足**最多有一个字符出现奇数次**。 正常做法是对每一个节点dfs,每到一个节点就强行枚举所有字母找到和他异或后结果为1的个数<1的路径,再去最长值,这样$O(n^2logn)$的,可以用dsu on tree优化到$O(nlog^2n)$.关于具体做法,可以参考下面的扩展阅读 正常做法是对每一个节点 dfs,每到一个节点就强行枚举所有字母找到和他异或后结果为1的个数<1的路径,再去最长值,这样 $O(n^2\log n)$ 的,可以用 dsu on tree 优化到 $O(n\log^2n)$。关于具体做法,可以参考下面的扩展阅读 2.可以用dsu乱搞~~吊打std~~水分的题 可以水一些树套树的部分分(没有修改操作),还可以把树上莫队的$O(n\sqrt{m})$吊着打 可以水一些树套树的部分分(没有修改操作),还可以把树上莫队的 $O(n\sqrt{m})$ 吊着打 ### 练习题 Loading