首页 > 其他分享 >点分治

点分治

时间:2024-11-04 09:23:46浏览次数:2  
标签:路径 int 分治 vis 端点 work root

点分治是个好东西。

P3806 【模板】点分治 1

给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在。

首先把询问离线。在之后的过程里一起统计答案。

树上距离 \(k\) 的点对,可以完全对应一条长度为 \(k\) 的路径。点分治就是处理这样一轮有关树上路径的问题。

不妨随便在树中选择一个点,然后分类讨论这条路径是否经过这个点。或者说,随便选一个点 \(root\) 作为树的根(这棵树原本是无根树),然后讨论路径是否经过根。这里到底选择哪个点作为树根后面再提。

如果这条路径不经过 \(root\),换言之这条路径的端点的 LCA 不为 \(root\)。那么将 \(root\) 删掉后,可以对于每个独立的连通块分别递归求解。或者说递归处理 \(root\) 的所有子树。

所以我们只需要求解第二种情况,即这条路径经过 \(root\)。

此时这条路径的两个端点会有两种情况:

  • 一个端点为 \(root\);
  • 两个端点都不为 \(root\)。

(还有一种两个端点都为 \(root\) 的情况太过简单。)

不难发现,后一种路径是由两条前一种路径构成的。例如 \(root \to A \to B\) 和 \(root \to C \to D\) 可以拼成 \(B \to A \to root \to C \to D\)。

但是,\(root \to A \to B\) 和 \(root \to A \to C\) 并无法组成一条合法的第二种路径。也就是说我们选择路径端点时,需要强制它们不在同一棵 \(root\) 的儿子的子树内

如何做到这个强制?很简单。我们按照顺序枚举 \(root\) 的儿子,然后枚举这个儿子的子树内的所有点,先尝试更新答案,再修改维护的数据结构(总结就是 先查询后修改)。例如:

void work(int u, int F) {
    // 查询
    {}
    for (int v : g[u])
        if (!vis[v] && v != F) {
            work(v, u);
        }
    // 修改
    {}  
}

void dfs(int u) {
    vis[u] = true;
    for (int v : g[u])
        if (!vis[v]) {
            work(v);
        }
  	// 清空
    {}
    for (int v : g[u])
        if (!vis[v]) {
            dfs(v);
        }
}

于是分别实现 // 查询 {}// 修改 {}// 清空 {} 就好了。


对于本题,我们只需要维护一个 bool 数组 \(f(i)\),表示是否出现过一个点距离 \(root\) 为 \(i\)。

那么查询和修改就只需要这样写:

res |= f[k - dis[u]];

f[dis[u]] = true;

\(dis(i)\) 表示点 \(i\) 到 \(u\) 的距离。这个是好维护的。

清空时直接 memset 复杂度不对。我们需要再进行类似上面的 work 函数。

void work(int u, int F) {
    f[dis[u]] = false;
    for (int v : g[u])
        if (!vis[v] && v != F) {
            work(v, u);
        }
}

至此,这个算法看起来很对。但是我们每访问到一个点,就去遍历它的所有子树,这样做复杂度实际上是 \(\mathcal O(n^2)\) 的(\(\sum_u size(u) = \mathcal O(n^2\)))。

如何优化?我们思考到底哪个点作为根 \(root\) 最优。

树的中心有性质:它的每个儿子的子树的大小 \(\le\) 总点数的一半。

如果选择树的中心作为根节点,那么最多递归 \(\log n\) 次,树的大小就会变成 \(0\)。(可以类比一下,如果树是一条链,那么与二分的过程很像。)

于是复杂度降至 \(\mathcal O(n \log n)\)。

标签:路径,int,分治,vis,端点,work,root
From: https://www.cnblogs.com/2huk/p/18524437

相关文章

  • C++优选算法 分治-快排
    一、基本思想快速排序采用分治法策略,将一个大数组(或子数组)分为两个小数组,然后递归地对这两个小数组进行排序。其基本思想可以概括为“分解、解决、合并”三个步骤:分解:将原问题(即待排序的数组)分解为若干个规模较小、相互独立且与原问题形式相同的子问题(即子数组)。解决:若子问题......
  • 树分治
    点分治思想回想序列分治的做法:递归统计两个区间,在统计跨两个区间的贡献对应到树上也类似:找一个分割点,统计子树的贡献,再统计跨子树的贡献由于路径都可以被某一级重心统计到,所以点分治长于做路径统计问题找树的中心树的中心:以它为根时的最大子树最小的点处理方式:开全局变量\(......
  • 算法设计与分析中的几个核心算法策略:动态规划、贪心算法、回溯算法和分治算法
    这些题目主要考察的是算法设计与分析中的几个核心算法策略:动态规划、贪心算法、回溯算法和分治算法。下面我将分别介绍这些知识点,并解析题目的详细解答过程。1.动态规划(DynamicProgramming,DP)知识点介绍:动态规划是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问......
  • 【算法】分治算法-让问题消失
    博主推荐!!!:"近期我偶然邂逅了一个极为出色的人工智能学习平台,它不仅内容深入浅出,讲解方式还风趣幽默,让人学习起来既轻松又高效。如此宝藏资源,我迫不及待想要与各位共享,让我们一起进入这个精彩纷呈的学习网站吧!"即刻点击https://www.captainbed.cn/cyy算法系列:今天,我们要聊的......
  • CDQ 分治学习笔记
    鲜花开新坑。早该卷卷了。集训队论文自认为写的很清晰。感觉对于一道自己进集训队时的国赛题能发明一种新做法很牛啊!简述CDQ分治是一种离线分治做法。CDQ分治并没有很固定的模板,这是一种分治思想的延伸。对于一道带修的数据结构问题,我们可以将每个查询视作其之前修改对......
  • 两棵树问题的一种点分治做法
    简述题面:你有两棵树,\(T_1\),\(T_2\),然后你需要对于每个点求出\(\min_{j\not=i}(dist(T_1,i,j)+dist(T_2,i,j))\)要求时间复杂度\(O(n\log^2n)\)或更优做法:考虑点分治,假如在\(T_1\)固定\(i,j\)一定要经过某个\(x\),然后把\(x\)作为分治点,那么实际上\(val[i,j]=......
  • 线段树分治学习笔记
    前置知识:线段树(只需要了解其结构)支持撤销的数据结构,如可撤销并查集,可持久化数据结构等。线段树分治是什么一种离线算法,用于处理假如某些信息会在某个时间段出现,求某个时刻的信息并。常见的离线算法还有CDQ分治,考虑一下这两个算法的区别。CDQ分治:对于每个操作,考虑其对后面询......
  • 分治法求最大连续子序列的积
    1.源代码#include<iostream>#include<vector>#include<string>#include<sstream>usingnamespacestd;intmax3(intnum1,intnum2,intnum3){   if(num1>num2){      num2=num1;   }   returnnum2>num3?num2:n......
  • 分治---归并排序
    参考资料水平有限,欢迎交流!【如何优雅地排序——3分钟看懂【归并排序】的基本思想】练习题P1177【模板】排序-洛谷|计算机科学教育新生态(luogu.com.cn)LCR170.交易逆序对的总数-力扣(LeetCode)关键步骤与应用步骤在归并过程中,主要包含两个关键步骤:分割(Divide):将......