点分治,基于点的分治;
其思路为:
子树结构:子树结构虽然的确是某点的一个子树,但我们讨论点分治时,相当于把这个子树摘下来,当做无根树处理;
对于一个子树结构:
- 处理子树结构的某个单点(未必是原根);
- 以这个被处理的点为新根,找出她的子树,变成递归下一层要处理的子树结构;
- 递归下一层;
在一个常见的,约定俗成的写法下(就是一个“说点分治就是指她”的写法)
其时间复杂度可以达到O(logn*f(num(1))
(num(i)为处理第i个单点时的数据范围);
(f(i)为处理数据范围为i的单点的时间复杂度);
(条件是f(i)是线性的);
这个复杂度是因为:
在递归分治的过程中,
对于每层,其num(i)之和与num(1)是同级的,
由于f(i)(往往)是线性的,故可以认为每层的各点的f(i)之和与f(num(1))同级,
在此基础上,我们使用里一种巧妙的办法,使得分治层数不超过logn层;
于是达到了需要的效率;
所以这个神奇的方法是什么呢?
考虑把每层的单点最大数量级,都变成上一层的一半;
这样就能使分治层数不超过logn时就不能再分了;
如何做到这点呢?
首先定义子树结构的重心:
在点分治中若在本层处理点i,可以使下一层处理的最大子树的节点最少,则点i为该子树结构的重心;
可以看出重心的节点最多的子树的节点数不会超过原子树结构的一半;
证明:
若重心的节点最多的子树的节点数超过了原子树结构的一半;
那么你看看以她的这个子树的根取代她作为重心的情况;
发现反而更有重心的样子;
于是这个重心是非法的;
所以重心的节点最多的子树的节点数不会超过原子树结构的一半;
于是我们用现在得到的方法重新修订、精细点分治的流程:
对于一个子树结构:
- 处理子树结构的重心;
- 以重心为新根,找出她的子树,变成递归下一层要处理的子树结构;
- 递归下一层;
这就是一个常规的约定俗成的却又效率优秀的点分治啦;
然后是一些实现:
分治的主体可以由dfs实现,
但跳转时,不会是直接跳到当前点的子节点,而是跳到当前点的子树重心上;
求重心:
两遍dfs实现:
inline void dfs_size(int now,int fa){ size[now]=1; for(int i=first[now];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=fa){ dfs_size(e[i].to,now); size[now]+=size[e[i].to]; } } inline int dfs_root(int now,int fa,int r){ int i,root=-1,wroot; max_size[now]=size[r]-size[now]; for(i=first[now];i;i=e[i].next) if(!vis[e[i].to]&&e[i].to!=fa){ if(size[e[i].to]>max_size[now]) max_size[now]=size[e[i].to]; wroot=dfs_root(e[i].to,now,r); if(max_size[wroot]<max_size[root]||root==-1) root=wroot; } if(max_size[now]<max_size[root]||root==-1) root=now; return root; }
例题:
题解
时间: 2024-11-05 07:20:47